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) { | func TestHtml(t *testing.T) { | ||||||
| //	Convey("Should load a document", t, func() { | 	Convey("Should load a document", t, func() { | ||||||
| //		c := compiler.New() | 		c := compiler.New() | ||||||
| // |  | ||||||
| //		out, err := c.MustCompile(` | 		out, err := c.MustCompile(` | ||||||
| //LET doc = DOCUMENT("https://github.com/", true) | LET doc = DOCUMENT("http://getbootstrap.com/docs/4.1/components/collapse/", true) | ||||||
| //LET main = ELEMENT(doc, '.application-main') |  | ||||||
| //LET mainTxt = main.innerText | CLICK(doc, "#headingTwo > h5 > button") | ||||||
| // | WAIT_CLASS(doc, "#collapseTwo", "bar") | ||||||
| //NAVIGATE(doc, "https://github.com/features") |  | ||||||
| // | RETURN TRUE | ||||||
| //LET features = ELEMENT(doc, '.application-main') | 			`).Run(context.Background()) | ||||||
| //LET featuresTxt = features.innerText |  | ||||||
| // | 		So(err, ShouldBeNil) | ||||||
| //LOG("featuresTxt:", featuresTxt) |  | ||||||
| // | 		So(string(out), ShouldEqual, `"int"`) | ||||||
| //RETURN mainTxt == featuresTxt | 	}) | ||||||
| // | } | ||||||
| // |  | ||||||
| //			`).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 { | func Error(err error, msg string) error { | ||||||
| 	return errors.Errorf("%s: %s", err.Error(), msg) | 	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 | 		HTMLNode | ||||||
|  |  | ||||||
| 		URL() core.Value | 		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. |  * 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. |  * If ``uniqueOnly`` is set to true, then will add the item only if it's unique. | ||||||
|  * @param arr (Array) - Target array. |  * @param arr (Array) - Target array. | ||||||
|  * @param item (Value) - Target value to add. |  * @param item (Read) - Target value to add. | ||||||
|  * @returns arr (Array) - New array. |  * @returns arr (Array) - New array. | ||||||
|  */ |  */ | ||||||
| func Append(_ context.Context, args ...core.Value) (core.Value, error) { | func Append(_ context.Context, args ...core.Value) (core.Value, error) { | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ import ( | |||||||
| /* | /* | ||||||
|  * Returns a first element from a given array. |  * Returns a first element from a given array. | ||||||
|  * @param arr (Array) - Target 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) { | func First(_ context.Context, args ...core.Value) (core.Value, error) { | ||||||
| 	err := core.ValidateArgs(args, 1, 1) | 	err := core.ValidateArgs(args, 1, 1) | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ import ( | |||||||
| /* | /* | ||||||
|  * Returns the last element of an array. |  * Returns the last element of an array. | ||||||
|  * @param array (Array) - The target 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) { | func Last(_ context.Context, args ...core.Value) (core.Value, error) { | ||||||
| 	err := core.ValidateArgs(args, 1, 1) | 	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. |  * 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 array (Array) - An array with elements of arbitrary type. | ||||||
|  * @param index (Int) - Position of desired element in array, positions start at 0. |  * @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. |  * 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) { | 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. |  * Returns a value indicating whether an element is contained in array. Optionally returns its position. | ||||||
|  * @param array (Array) - The source array. |  * @param array (Array) - The source array. | ||||||
|  * @param value (Value) - The target value. |  * @param value (Read) - The target value. | ||||||
|  * @param returnIndex (Boolean, optional) - Value which indicates whether to return item's position. |  * @param returnIndex (Boolean, optional) - Read which indicates whether to return item's position. | ||||||
|  */ |  */ | ||||||
| func Position(_ context.Context, args ...core.Value) (core.Value, error) { | func Position(_ context.Context, args ...core.Value) (core.Value, error) { | ||||||
| 	err := core.ValidateArgs(args, 2, 3) | 	err := core.ValidateArgs(args, 2, 3) | ||||||
|   | |||||||
| @@ -9,8 +9,8 @@ import ( | |||||||
| /* | /* | ||||||
|  * Create a new array with appended value. |  * Create a new array with appended value. | ||||||
|  * @param array (Array) - Source array. |  * @param array (Array) - Source array. | ||||||
|  * @param value (Value) - Target value. |  * @param value (Read) - Target value. | ||||||
|  * @param unique (Boolean, optional) - Value indicating whether to do uniqueness check. |  * @param unique (Boolean, optional) - Read indicating whether to do uniqueness check. | ||||||
|  * @returns (Array) - A new array with appended value. |  * @returns (Array) - A new array with appended value. | ||||||
|  */ |  */ | ||||||
| func Push(_ context.Context, args ...core.Value) (core.Value, error) { | 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. |  * Returns a new array with removed all occurrences of value in a given array. | ||||||
|  * Optionally with a limit to the number of removals. |  * Optionally with a limit to the number of removals. | ||||||
|  * @param array (Array) - Source array. |  * @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. |  * @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. |  * @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. |  * Returns a new sliced array. | ||||||
|  * @param array (Array) - Source array. |  * @param array (Array) - Source array. | ||||||
|  * @param start (Int) - Start position of extraction. |  * @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. |  * @returns (Array) - Sliced array. | ||||||
|  */ |  */ | ||||||
| func Slice(_ context.Context, args ...core.Value) (core.Value, error) { | func Slice(_ context.Context, args ...core.Value) (core.Value, error) { | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ import ( | |||||||
| /* | /* | ||||||
|  * Prepends value to a given array. |  * Prepends value to a given array. | ||||||
|  * @param array (Array) - Target 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. |  * @param unique (Boolean, optional) - Optional value indicating whether a value must be unique to be prepended. | ||||||
|  * Default is false. |  * Default is false. | ||||||
|  * @returns (Array) - New array with prepended value. |  * @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/core" | ||||||
| 	"github.com/MontFerret/ferret/pkg/runtime/values" | 	"github.com/MontFerret/ferret/pkg/runtime/values" | ||||||
| 	"github.com/MontFerret/ferret/pkg/stdlib/html/driver" | 	"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 { | 	if err != nil { | ||||||
| 		return values.None, err | 		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 | 	var drv driver.Driver | ||||||
|  |  | ||||||
| 	if !dynamic { | 	if !dynamic { | ||||||
| @@ -27,56 +49,5 @@ func Document(ctx context.Context, inputs ...core.Value) (core.Value, error) { | |||||||
| 		return values.None, err | 		return values.None, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return drv.GetDocument(ctx, url.String()) | 	return drv.GetDocument(ctx, url) | ||||||
| } |  | ||||||
|  |  | ||||||
| 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 |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										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 | package common | ||||||
|  |  | ||||||
|  | import "strings" | ||||||
|  |  | ||||||
| var Attributes = []string{ | var Attributes = []string{ | ||||||
| 	"abbr", | 	"abbr", | ||||||
| 	"accept", | 	"accept", | ||||||
| @@ -147,7 +149,12 @@ func init() { | |||||||
| } | } | ||||||
|  |  | ||||||
| func IsAttribute(name string) bool { | 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 | 	return lz | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Indicates whether the value is ready. | ||||||
|  |  * @returns (Boolean) - Boolean value indicating whether the value is ready. | ||||||
|  |  */ | ||||||
| func (lv *LazyValue) Ready() bool { | func (lv *LazyValue) Ready() bool { | ||||||
| 	lv.Lock() | 	lv.Lock() | ||||||
| 	defer lv.Unlock() | 	defer lv.Unlock() | ||||||
| @@ -34,25 +38,42 @@ func (lv *LazyValue) Ready() bool { | |||||||
| 	return lv.ready | 	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() | 	lv.Lock() | ||||||
| 	defer lv.Unlock() | 	defer lv.Unlock() | ||||||
|  |  | ||||||
| 	if !lv.ready { | 	if !lv.ready { | ||||||
| 		val, err := lv.factory() | 		lv.load() | ||||||
|  |  | ||||||
| 		if err == nil { |  | ||||||
| 			lv.value = val |  | ||||||
| 			lv.err = nil |  | ||||||
| 		} else { |  | ||||||
| 			lv.value = values.None |  | ||||||
| 			lv.err = err |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return lv.value, lv.err | 	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() { | func (lv *LazyValue) Reset() { | ||||||
| 	lv.Lock() | 	lv.Lock() | ||||||
| 	defer lv.Unlock() | 	defer lv.Unlock() | ||||||
| @@ -61,3 +82,17 @@ func (lv *LazyValue) Reset() { | |||||||
| 	lv.value = values.None | 	lv.value = values.None | ||||||
| 	lv.err = nil | 	lv.err = nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (lv *LazyValue) load() { | ||||||
|  | 	val, err := lv.factory() | ||||||
|  |  | ||||||
|  | 	if err == nil { | ||||||
|  | 		lv.value = val | ||||||
|  | 		lv.err = nil | ||||||
|  | 	} else { | ||||||
|  | 		lv.value = values.None | ||||||
|  | 		lv.err = err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	lv.ready = true | ||||||
|  | } | ||||||
|   | |||||||
| @@ -9,23 +9,23 @@ import ( | |||||||
| 	"github.com/MontFerret/ferret/pkg/stdlib/html/driver/static" | 	"github.com/MontFerret/ferret/pkg/stdlib/html/driver/static" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type DriverName string | type Name string | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| 	Dynamic DriverName = "dynamic" | 	Dynamic Name = "dynamic" | ||||||
| 	Static  DriverName = "static" | 	Static  Name = "static" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type Driver interface { | type Driver interface { | ||||||
| 	GetDocument(ctx context.Context, url string) (values.HTMLNode, error) | 	GetDocument(ctx context.Context, url values.String) (values.HTMLNode, error) | ||||||
| 	Close() 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) | 	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) | 	val := ctx.Value(name) | ||||||
|  |  | ||||||
| 	drv, ok := val.(Driver) | 	drv, ok := val.(Driver) | ||||||
|   | |||||||
| @@ -21,7 +21,6 @@ import ( | |||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| const BlankPageURL = "about:blank" | const BlankPageURL = "about:blank" | ||||||
|  |  | ||||||
| type HTMLDocument struct { | type HTMLDocument struct { | ||||||
| @@ -378,7 +377,7 @@ func (doc *HTMLDocument) URL() core.Value { | |||||||
| 	return doc.url | 	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( | 	res, err := eval.Eval( | ||||||
| 		doc.client, | 		doc.client, | ||||||
| 		fmt.Sprintf(` | 		fmt.Sprintf(` | ||||||
| @@ -395,17 +394,23 @@ func (doc *HTMLDocument) InnerHTMLBySelector(selector values.String) (values.Str | |||||||
| 	) | 	) | ||||||
|  |  | ||||||
| 	if err != nil { | 	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 { | 	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( | 	res, err := eval.Eval( | ||||||
| 		doc.client, | 		doc.client, | ||||||
| 		fmt.Sprintf(` | 		fmt.Sprintf(` | ||||||
| @@ -427,17 +432,23 @@ func (doc *HTMLDocument) InnerHTMLBySelectorAll(selector values.String) (*values | |||||||
| 	) | 	) | ||||||
|  |  | ||||||
| 	if err != nil { | 	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 { | 	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( | 	res, err := eval.Eval( | ||||||
| 		doc.client, | 		doc.client, | ||||||
| 		fmt.Sprintf(` | 		fmt.Sprintf(` | ||||||
| @@ -454,17 +465,23 @@ func (doc *HTMLDocument) InnerTextBySelector(selector values.String) (values.Str | |||||||
| 	) | 	) | ||||||
|  |  | ||||||
| 	if err != nil { | 	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 { | 	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( | 	res, err := eval.Eval( | ||||||
| 		doc.client, | 		doc.client, | ||||||
| 		fmt.Sprintf(` | 		fmt.Sprintf(` | ||||||
| @@ -486,14 +503,20 @@ func (doc *HTMLDocument) InnerTextBySelectorAll(selector values.String) (*values | |||||||
| 	) | 	) | ||||||
|  |  | ||||||
| 	if err != nil { | 	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 { | 	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) { | 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 { | func (doc *HTMLDocument) WaitForSelector(selector values.String, timeout values.Int) error { | ||||||
| 	task := events.NewWaitTask( | 	task := events.NewEvalWaitTask( | ||||||
| 		doc.client, | 		doc.client, | ||||||
| 		fmt.Sprintf(` | 		fmt.Sprintf(` | ||||||
| 			el = document.querySelector(%s); | 			var el = document.querySelector(%s); | ||||||
|  |  | ||||||
| 			if (el != null) { | 			if (el != null) { | ||||||
| 				return true; | 				return true; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			// null means we need to repeat | ||||||
| 			return null; | 			return null; | ||||||
| 		`, eval.ParamString(selector.String())), | 		`, eval.ParamString(selector.String())), | ||||||
| 		time.Millisecond*time.Duration(timeout), | 		time.Millisecond*time.Duration(timeout), | ||||||
| @@ -615,6 +639,78 @@ func (doc *HTMLDocument) WaitForSelector(selector values.String, timeout values. | |||||||
| 	return err | 	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 { | func (doc *HTMLDocument) WaitForNavigation(timeout values.Int) error { | ||||||
| 	timer := time.NewTimer(time.Millisecond * time.Duration(timeout)) | 	timer := time.NewTimer(time.Millisecond * time.Duration(timeout)) | ||||||
| 	onEvent := make(chan bool) | 	onEvent := make(chan bool) | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ func NewDriver(address string) *Driver { | |||||||
| 	return drv | 	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) | 	err := drv.init(ctx) | ||||||
|  |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -38,6 +38,8 @@ func (drv *Driver) GetDocument(ctx context.Context, url string) (values.HTMLNode | |||||||
| 	ctx, cancel := context.WithTimeout(ctx, DefaultTimeout) | 	ctx, cancel := context.WithTimeout(ctx, DefaultTimeout) | ||||||
| 	defer cancel() | 	defer cancel() | ||||||
|  |  | ||||||
|  | 	url := targetURL.String() | ||||||
|  |  | ||||||
| 	if url == "" { | 	if url == "" { | ||||||
| 		url = BlankPageURL | 		url = BlankPageURL | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ import ( | |||||||
| 	"github.com/rs/zerolog" | 	"github.com/rs/zerolog" | ||||||
| 	"hash/fnv" | 	"hash/fnv" | ||||||
| 	"strconv" | 	"strconv" | ||||||
|  | 	"strings" | ||||||
| 	"sync" | 	"sync" | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
| @@ -145,7 +146,7 @@ func (el *HTMLElement) Type() core.Type { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (el *HTMLElement) MarshalJSON() ([]byte, error) { | func (el *HTMLElement) MarshalJSON() ([]byte, error) { | ||||||
| 	val, err := el.innerText.Value() | 	val, err := el.innerText.Read() | ||||||
|  |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -188,7 +189,6 @@ func (el *HTMLElement) Unwrap() interface{} { | |||||||
| 	return el | 	return el | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| func (el *HTMLElement) Hash() uint64 { | func (el *HTMLElement) Hash() uint64 { | ||||||
| 	el.Lock() | 	el.Lock() | ||||||
| 	defer el.Unlock() | 	defer el.Unlock() | ||||||
| @@ -243,7 +243,7 @@ func (el *HTMLElement) NodeName() values.String { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (el *HTMLElement) GetAttributes() core.Value { | func (el *HTMLElement) GetAttributes() core.Value { | ||||||
| 	val, err := el.attributes.Value() | 	val, err := el.attributes.Read() | ||||||
|  |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return values.None | 		return values.None | ||||||
| @@ -254,7 +254,7 @@ func (el *HTMLElement) GetAttributes() core.Value { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (el *HTMLElement) GetAttribute(name values.String) core.Value { | func (el *HTMLElement) GetAttribute(name values.String) core.Value { | ||||||
| 	attrs, err := el.attributes.Value() | 	attrs, err := el.attributes.Read() | ||||||
|  |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return values.None | 		return values.None | ||||||
| @@ -270,7 +270,7 @@ func (el *HTMLElement) GetAttribute(name values.String) core.Value { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (el *HTMLElement) GetChildNodes() core.Value { | func (el *HTMLElement) GetChildNodes() core.Value { | ||||||
| 	val, err := el.loadedChildren.Value() | 	val, err := el.loadedChildren.Read() | ||||||
|  |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return values.NewArray(0) | 		return values.NewArray(0) | ||||||
| @@ -280,7 +280,7 @@ func (el *HTMLElement) GetChildNodes() core.Value { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (el *HTMLElement) GetChildNode(idx values.Int) core.Value { | func (el *HTMLElement) GetChildNode(idx values.Int) core.Value { | ||||||
| 	val, err := el.loadedChildren.Value() | 	val, err := el.loadedChildren.Read() | ||||||
|  |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return values.None | 		return values.None | ||||||
| @@ -365,8 +365,38 @@ func (el *HTMLElement) QuerySelectorAll(selector values.String) core.Value { | |||||||
| 	return arr | 	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 { | func (el *HTMLElement) InnerText() values.String { | ||||||
| 	val, err := el.innerText.Value() | 	val, err := el.innerText.Read() | ||||||
|  |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return values.EmptyString | 		return values.EmptyString | ||||||
| @@ -469,29 +499,25 @@ func (el *HTMLElement) handleAttrModified(message interface{}) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// they are not event loaded | 	el.attributes.Write(func(v core.Value, err error) { | ||||||
| 	// just ignore the event | 		if err != nil { | ||||||
| 	if !el.attributes.Ready() { | 			el.logger.Error(). | ||||||
| 		return | 				Timestamp(). | ||||||
| 	} | 				Err(err). | ||||||
|  | 				Int("id", int(el.id)). | ||||||
|  | 				Msg("failed to update node") | ||||||
|  |  | ||||||
| 	val, err := el.attributes.Value() | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	// failed to load | 		attrs, ok := v.(*values.Object) | ||||||
| 	if err != nil { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	el.Lock() | 		if !ok { | ||||||
| 	defer el.Unlock() | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	attrs, ok := val.(*values.Object) | 		attrs.Set(values.NewString(reply.Name), values.NewString(reply.Value)) | ||||||
|  | 	}) | ||||||
| 	if !ok { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	attrs.Set(values.NewString(reply.Name), values.NewString(reply.Value)) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (el *HTMLElement) handleAttrRemoved(message interface{}) { | func (el *HTMLElement) handleAttrRemoved(message interface{}) { | ||||||
| @@ -513,24 +539,25 @@ func (el *HTMLElement) handleAttrRemoved(message interface{}) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	val, err := el.attributes.Value() | 	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") | ||||||
|  |  | ||||||
| 	// failed to load | 			return | ||||||
| 	if err != nil { | 		} | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	el.Lock() | 		attrs, ok := v.(*values.Object) | ||||||
| 	defer el.Unlock() |  | ||||||
|  |  | ||||||
| 	attrs, ok := val.(*values.Object) | 		if !ok { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	if !ok { | 		attrs.Remove(values.NewString(reply.Name)) | ||||||
| 		return | 	}) | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// TODO: actually, we need to sync it too... |  | ||||||
| 	attrs.Remove(values.NewString(reply.Name)) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (el *HTMLElement) handleChildrenCountChanged(message interface{}) { | func (el *HTMLElement) handleChildrenCountChanged(message interface{}) { | ||||||
| @@ -598,40 +625,36 @@ func (el *HTMLElement) handleChildInserted(message interface{}) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	loaded, err := el.loadedChildren.Value() | 	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 { | 		if err != nil { | ||||||
| 		return | 			el.logger.Error(). | ||||||
| 	} | 				Timestamp(). | ||||||
|  | 				Err(err). | ||||||
|  | 				Int("id", int(el.id)). | ||||||
|  | 				Msg("failed to load an inserted node") | ||||||
|  |  | ||||||
| 	loadedArr := loaded.(*values.Array) | 			return | ||||||
| 	loadedEl, err := LoadElement(el.logger, el.client, el.broker, nextID) | 		} | ||||||
|  |  | ||||||
| 	if err != nil { | 		loadedArr.Insert(values.NewInt(targetIDx), loadedEl) | ||||||
| 		el.logger.Error(). |  | ||||||
| 			Timestamp(). |  | ||||||
| 			Err(err). |  | ||||||
| 			Int("id", int(el.id)). |  | ||||||
| 			Msg("failed to load an inserted node") |  | ||||||
|  |  | ||||||
| 		return | 		newInnerHTML, err := loadInnerHTML(el.client, el.id) | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	loadedArr.Insert(values.NewInt(targetIDx), loadedEl) | 		if err != nil { | ||||||
|  | 			el.logger.Error(). | ||||||
|  | 				Timestamp(). | ||||||
|  | 				Err(err). | ||||||
|  | 				Int("id", int(el.id)). | ||||||
|  | 				Msg("failed to update node") | ||||||
|  |  | ||||||
| 	newInnerHTML, err := loadInnerHTML(el.client, el.id) | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	if err != nil { | 		el.innerHTML = newInnerHTML | ||||||
| 		el.logger.Error(). | 	}) | ||||||
| 			Timestamp(). |  | ||||||
| 			Err(err). |  | ||||||
| 			Int("id", int(el.id)). |  | ||||||
| 			Msg("failed to update node") |  | ||||||
|  |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	el.innerHTML = newInnerHTML |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (el *HTMLElement) handleChildDeleted(message interface{}) { | func (el *HTMLElement) handleChildDeleted(message interface{}) { | ||||||
| @@ -669,26 +692,32 @@ func (el *HTMLElement) handleChildDeleted(message interface{}) { | |||||||
| 		return | 		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") | ||||||
|  |  | ||||||
| 	if err != nil { | 			return | ||||||
| 		return | 		} | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	loadedArr := loaded.(*values.Array) | 		loadedArr := v.(*values.Array) | ||||||
| 	loadedArr.RemoveAt(values.NewInt(targetIDx)) | 		loadedArr.RemoveAt(values.NewInt(targetIDx)) | ||||||
|  |  | ||||||
| 	newInnerHTML, err := loadInnerHTML(el.client, el.id) | 		newInnerHTML, err := loadInnerHTML(el.client, el.id) | ||||||
|  |  | ||||||
| 	if err != nil { | 		if err != nil { | ||||||
| 		el.logger.Error(). | 			el.logger.Error(). | ||||||
| 			Timestamp(). | 				Timestamp(). | ||||||
| 			Err(err). | 				Err(err). | ||||||
| 			Int("id", int(el.id)). | 				Int("id", int(el.id)). | ||||||
| 			Msg("failed to update node") | 				Msg("failed to update node") | ||||||
|  |  | ||||||
| 		return | 			return | ||||||
| 	} | 		} | ||||||
|  |  | ||||||
| 	el.innerHTML = newInnerHTML | 		el.innerHTML = newInnerHTML | ||||||
|  | 	}) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -8,24 +8,24 @@ import ( | |||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type WaitTask struct { | type ( | ||||||
| 	client    *cdp.Client | 	Function func() (core.Value, error) | ||||||
| 	predicate string | 	WaitTask struct { | ||||||
| 	timeout   time.Duration | 		fun     Function | ||||||
| 	polling   time.Duration | 		timeout time.Duration | ||||||
| } | 		polling time.Duration | ||||||
|  | 	} | ||||||
|  | ) | ||||||
|  |  | ||||||
| const DefaultPolling = time.Millisecond * time.Duration(200) | const DefaultPolling = time.Millisecond * time.Duration(200) | ||||||
|  |  | ||||||
| func NewWaitTask( | func NewWaitTask( | ||||||
| 	client *cdp.Client, | 	fun Function, | ||||||
| 	predicate string, |  | ||||||
| 	timeout time.Duration, | 	timeout time.Duration, | ||||||
| 	polling time.Duration, | 	polling time.Duration, | ||||||
| ) *WaitTask { | ) *WaitTask { | ||||||
| 	return &WaitTask{ | 	return &WaitTask{ | ||||||
| 		client, | 		fun, | ||||||
| 		predicate, |  | ||||||
| 		timeout, | 		timeout, | ||||||
| 		polling, | 		polling, | ||||||
| 	} | 	} | ||||||
| @@ -39,14 +39,9 @@ func (task *WaitTask) Run() (core.Value, error) { | |||||||
| 		case <-timer.C: | 		case <-timer.C: | ||||||
| 			return values.None, core.ErrTimeout | 			return values.None, core.ErrTimeout | ||||||
| 		default: | 		default: | ||||||
| 			out, err := eval.Eval( | 			out, err := task.fun() | ||||||
| 				task.client, |  | ||||||
| 				task.predicate, |  | ||||||
| 				true, |  | ||||||
| 				false, |  | ||||||
| 			) |  | ||||||
|  |  | ||||||
| 			// JS expression failed | 			// expression failed | ||||||
| 			// terminating | 			// terminating | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				timer.Stop() | 				timer.Stop() | ||||||
| @@ -54,7 +49,7 @@ func (task *WaitTask) Run() (core.Value, error) { | |||||||
| 				return values.None, err | 				return values.None, err | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// JS output is not empty | 			// output is not empty | ||||||
| 			// terminating | 			// terminating | ||||||
| 			if out != values.None { | 			if out != values.None { | ||||||
| 				timer.Stop() | 				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/mafredri/cdp/protocol/page" | ||||||
| 	"github.com/rs/zerolog" | 	"github.com/rs/zerolog" | ||||||
| 	"golang.org/x/sync/errgroup" | 	"golang.org/x/sync/errgroup" | ||||||
|  | 	"strings" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func pointerInt(input int) *int { | func pointerInt(input int) *int { | ||||||
| @@ -34,6 +35,7 @@ func parseAttrs(attrs []string) *values.Object { | |||||||
| 	res := values.NewObject() | 	res := values.NewObject() | ||||||
|  |  | ||||||
| 	for _, el := range attrs { | 	for _, el := range attrs { | ||||||
|  | 		el = strings.TrimSpace(el) | ||||||
| 		str := values.NewString(el) | 		str := values.NewString(el) | ||||||
|  |  | ||||||
| 		if common.IsAttribute(el) { | 		if common.IsAttribute(el) { | ||||||
| @@ -43,7 +45,11 @@ func parseAttrs(attrs []string) *values.Object { | |||||||
| 			current, ok := res.Get(attr) | 			current, ok := res.Get(attr) | ||||||
|  |  | ||||||
| 			if ok { | 			if ok { | ||||||
| 				res.Set(attr, current.(values.String).Concat(values.SpaceString).Concat(str)) | 				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 | 	return &HTMLDocument{el, values.NewString(url)}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (el *HTMLDocument) Type() core.Type { | func (doc *HTMLDocument) Type() core.Type { | ||||||
| 	return core.HTMLDocumentType | 	return core.HTMLDocumentType | ||||||
| } | } | ||||||
|  |  | ||||||
| func (el *HTMLDocument) Compare(other core.Value) int { | func (doc *HTMLDocument) Compare(other core.Value) int { | ||||||
| 	switch other.Type() { | 	switch other.Type() { | ||||||
| 	case core.HTMLDocumentType: | 	case core.HTMLDocumentType: | ||||||
| 		otherDoc := other.(values.HTMLDocument) | 		otherDoc := other.(values.HTMLDocument) | ||||||
|  |  | ||||||
| 		return el.url.Compare(otherDoc.URL()) | 		return doc.url.Compare(otherDoc.URL()) | ||||||
| 	default: | 	default: | ||||||
| 		if other.Type() > core.HTMLDocumentType { | 		if other.Type() > core.HTMLDocumentType { | ||||||
| 			return -1 | 			return -1 | ||||||
| @@ -51,6 +51,52 @@ func (el *HTMLDocument) Compare(other core.Value) int { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (el *HTMLDocument) URL() core.Value { | func (doc *HTMLDocument) URL() core.Value { | ||||||
| 	return el.url | 	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 | 	return el.selection | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| func (el *HTMLElement) Hash() uint64 { | func (el *HTMLElement) Hash() uint64 { | ||||||
| 	str, err := el.selection.Html() | 	str, err := el.selection.Html() | ||||||
|  |  | ||||||
|   | |||||||
| @@ -297,7 +297,7 @@ func TestElement(t *testing.T) { | |||||||
| 		So(el.Length(), ShouldEqual, 4) | 		So(el.Length(), ShouldEqual, 4) | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	Convey(".Value", t, func() { | 	Convey(".Read", t, func() { | ||||||
| 		buff := bytes.NewBuffer([]byte(` | 		buff := bytes.NewBuffer([]byte(` | ||||||
| 			<html> | 			<html> | ||||||
| 				<head></head> | 				<head></head> | ||||||
|   | |||||||
| @@ -28,7 +28,8 @@ func NewDriver(setters ...Option) *Driver { | |||||||
| 	return &Driver{client} | 	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) | 	req, err := httpx.NewRequest(httpx.MethodGet, url, nil) | ||||||
|  |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -58,7 +59,7 @@ func (d *Driver) GetDocument(_ context.Context, url string) (values.HTMLNode, er | |||||||
| 	return NewHTMLDocument(url, doc) | 	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)) | 	buf := bytes.NewBuffer([]byte(str)) | ||||||
|  |  | ||||||
| 	doc, err := goquery.NewDocumentFromReader(buf) | 	doc, err := goquery.NewDocumentFromReader(buf) | ||||||
|   | |||||||
| @@ -6,8 +6,15 @@ import ( | |||||||
| 	"github.com/MontFerret/ferret/pkg/runtime/values" | 	"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 { | 	if err != nil { | ||||||
| 		return values.None, err | 		return values.None, err | ||||||
| @@ -16,36 +23,24 @@ func Element(_ context.Context, inputs ...core.Value) (core.Value, error) { | |||||||
| 	return el.QuerySelector(selector), nil | 	return el.QuerySelector(selector), nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func Elements(_ context.Context, inputs ...core.Value) (core.Value, error) { | func queryArgs(args []core.Value) (values.HTMLNode, values.String, error) { | ||||||
| 	el, selector, err := elementArgs(inputs) | 	err := core.ValidateArgs(args, 2, 2) | ||||||
|  |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return values.None, err | 		return nil, values.EmptyString, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return el.QuerySelectorAll(selector), nil | 	err = core.ValidateType(args[0], core.HTMLDocumentType, core.HTMLElementType) | ||||||
| } |  | ||||||
|  | 	if err != nil { | ||||||
| func elementArgs(inputs []core.Value) (values.HTMLNode, values.String, error) { | 		return nil, values.EmptyString, err | ||||||
| 	if len(inputs) == 0 { | 	} | ||||||
| 		return nil, values.EmptyString, core.Error(core.ErrMissedArgument, "element and arg2") |  | ||||||
| 	} | 	err = core.ValidateType(args[1], core.StringType) | ||||||
|  |  | ||||||
| 	if len(inputs) == 1 { | 	if err != nil { | ||||||
| 		return nil, values.EmptyString, core.Error(core.ErrMissedArgument, "arg2") | 		return nil, values.EmptyString, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	arg1 := inputs[0] | 	return args[0].(values.HTMLNode), args[1].(values.String), nil | ||||||
| 	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 |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										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 | 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 { | func NewLib() map[string]core.Function { | ||||||
| 	return map[string]core.Function{ | 	return map[string]core.Function{ | ||||||
| @@ -10,6 +19,8 @@ func NewLib() map[string]core.Function { | |||||||
| 		"ELEMENTS":        Elements, | 		"ELEMENTS":        Elements, | ||||||
| 		"WAIT_ELEMENT":    WaitElement, | 		"WAIT_ELEMENT":    WaitElement, | ||||||
| 		"WAIT_NAVIGATION": WaitNavigation, | 		"WAIT_NAVIGATION": WaitNavigation, | ||||||
|  | 		"WAIT_CLASS":      WaitClass, | ||||||
|  | 		"WAIT_CLASS_ALL":  WaitClassAll, | ||||||
| 		"CLICK":           Click, | 		"CLICK":           Click, | ||||||
| 		"CLICK_ALL":       ClickAll, | 		"CLICK_ALL":       ClickAll, | ||||||
| 		"NAVIGATE":        Navigate, | 		"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. |  * @params src (String...|Array) - The source string / array. | ||||||
|  * @returns String |  * @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 separator (string) - The separator string. | ||||||
|  * @params src (string...|array) - The source string / array. |  * @params src (string...|array) - The source string / array. | ||||||
|  * @returns string |  * @returns string | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ import ( | |||||||
| /* | /* | ||||||
|  * Returns a FQL value described by the JSON-encoded input string. |  * Returns a FQL value described by the JSON-encoded input string. | ||||||
|  * @params text (String) - The string to parse as JSON. |  * @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) { | func JSONParse(_ context.Context, args ...core.Value) (core.Value, error) { | ||||||
| 	err := core.ValidateArgs(args, 1, 1) | 	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. |  * 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) |  * @returns json (String) | ||||||
|  */ |  */ | ||||||
| func JSONStringify(_ context.Context, args ...core.Value) (core.Value, error) { | func JSONStringify(_ context.Context, args ...core.Value) (core.Value, error) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user