extlibs

package
v0.2.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 3, 2026 License: MIT Imports: 37 Imported by: 0

Documentation

Overview

Package extlibs provides external libraries that need explicit registration

Package extlibs provides external libraries that need explicit registration

Index

Constants

View Source
const (
	RequestsLibraryName   = "requests"
	SysLibraryName        = "sys"
	SecretsLibraryName    = "secrets"
	SubprocessLibraryName = "subprocess"
	HTMLParserLibraryName = "html.parser"
	OSLibraryName         = "os"
	OSPathLibraryName     = "os.path"
	PathlibLibraryName    = "pathlib"
	GlobLibraryName       = "glob"
	LoggingLibraryName    = "logging"
	WaitForLibraryName    = "wait_for"
	AILibraryName         = "scriptling.ai"
	MCPLibraryName        = "scriptling.mcp"
	ToonLibraryName       = "scriptling.toon"
	YAMLLibraryName       = "yaml"
	AgentLibraryName      = "scriptling.ai.agent"
	InteractLibraryName   = "scriptling.ai.agent.interact"
	FuzzyLibraryName      = "scriptling.fuzzy"
	TOMLLibraryName       = "toml"
)

Library names as constants for easy reference

View Source
const RuntimeLibraryName = "scriptling.runtime"

Variables

View Source
var CompletedProcessClass = &object.Class{
	Name: "CompletedProcess",
	Methods: map[string]object.Object{
		"check_returncode": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 1); err != nil {
					return err
				}
				if instance, ok := args[0].(*object.Instance); ok {
					if returncode, ok := instance.Fields["returncode"].(*object.Integer); ok {
						if returncode.Value != 0 {
							return errors.NewError("Command returned non-zero exit status %d", returncode.Value)
						}
						return args[0]
					}
				}
				return errors.NewError("Invalid CompletedProcess instance")
			},
			HelpText: `check_returncode() - Check if the process returned successfully

Raises an exception if returncode is non-zero.`,
		},
	},
}

CompletedProcessClass defines the CompletedProcess class

View Source
var HTMLParserLibrary = object.NewLibrary(HTMLParserLibraryName, nil, map[string]object.Object{
	"HTMLParser": &object.Class{
		Name:    "HTMLParser",
		Methods: htmlParserMethods,
	},
}, "HTML parser library compatible with Python's html.parser module")

HTMLParserLibrary provides Python-compatible html.parser functionality

View Source
var HTTPSubLibrary = object.NewLibrary("http", map[string]*object.Builtin{
	"get": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 2); err != nil {
				return err
			}

			path, err := args[0].AsString()
			if err != nil {
				return err
			}

			handler, err := args[1].AsString()
			if err != nil {
				return err
			}

			RuntimeState.Lock()
			RuntimeState.Routes[path] = &RouteInfo{
				Methods: []string{"GET"},
				Handler: handler,
			}
			RuntimeState.Unlock()

			return &object.Null{}
		},
		HelpText: `get(path, handler) - Register a GET route

Parameters:
  path (string): URL path for the route (e.g., "/api/users")
  handler (string): Handler function as "library.function" string

Example:
  runtime.http.get("/health", "handlers.health_check")`,
	},

	"post": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 2); err != nil {
				return err
			}

			path, err := args[0].AsString()
			if err != nil {
				return err
			}

			handler, err := args[1].AsString()
			if err != nil {
				return err
			}

			RuntimeState.Lock()
			RuntimeState.Routes[path] = &RouteInfo{
				Methods: []string{"POST"},
				Handler: handler,
			}
			RuntimeState.Unlock()

			return &object.Null{}
		},
		HelpText: `post(path, handler) - Register a POST route

Parameters:
  path (string): URL path for the route
  handler (string): Handler function as "library.function" string

Example:
  runtime.http.post("/webhook", "handlers.webhook")`,
	},

	"put": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 2); err != nil {
				return err
			}

			path, err := args[0].AsString()
			if err != nil {
				return err
			}

			handler, err := args[1].AsString()
			if err != nil {
				return err
			}

			RuntimeState.Lock()
			RuntimeState.Routes[path] = &RouteInfo{
				Methods: []string{"PUT"},
				Handler: handler,
			}
			RuntimeState.Unlock()

			return &object.Null{}
		},
		HelpText: `put(path, handler) - Register a PUT route

Parameters:
  path (string): URL path for the route
  handler (string): Handler function as "library.function" string

Example:
  runtime.http.put("/resource", "handlers.update_resource")`,
	},

	"delete": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 2); err != nil {
				return err
			}

			path, err := args[0].AsString()
			if err != nil {
				return err
			}

			handler, err := args[1].AsString()
			if err != nil {
				return err
			}

			RuntimeState.Lock()
			RuntimeState.Routes[path] = &RouteInfo{
				Methods: []string{"DELETE"},
				Handler: handler,
			}
			RuntimeState.Unlock()

			return &object.Null{}
		},
		HelpText: `delete(path, handler) - Register a DELETE route

Parameters:
  path (string): URL path for the route
  handler (string): Handler function as "library.function" string

Example:
  runtime.http.delete("/resource", "handlers.delete_resource")`,
	},

	"route": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 2); err != nil {
				return err
			}

			path, err := args[0].AsString()
			if err != nil {
				return err
			}

			handler, err := args[1].AsString()
			if err != nil {
				return err
			}

			var methods []string
			if m := kwargs.Get("methods"); m != nil {
				if list, e := m.AsList(); e == nil {
					for _, item := range list {
						if method, e := item.AsString(); e == nil {
							methods = append(methods, strings.ToUpper(method))
						}
					}
				}
			}
			if len(methods) == 0 {
				methods = []string{"GET", "POST", "PUT", "DELETE"}
			}

			RuntimeState.Lock()
			RuntimeState.Routes[path] = &RouteInfo{
				Methods: methods,
				Handler: handler,
			}
			RuntimeState.Unlock()

			return &object.Null{}
		},
		HelpText: `route(path, handler, methods=["GET", "POST", "PUT", "DELETE"]) - Register a route for multiple methods

Parameters:
  path (string): URL path for the route
  handler (string): Handler function as "library.function" string
  methods (list): List of HTTP methods to accept

Example:
  runtime.http.route("/api", "handlers.api", methods=["GET", "POST"])`,
	},

	"middleware": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 1); err != nil {
				return err
			}

			handler, err := args[0].AsString()
			if err != nil {
				return err
			}

			RuntimeState.Lock()
			RuntimeState.Middleware = handler
			RuntimeState.Unlock()

			return &object.Null{}
		},
		HelpText: `middleware(handler) - Register middleware for all routes

Parameters:
  handler (string): Middleware function as "library.function" string

The middleware receives the request object and should return:
  - None to continue to the handler
  - A response dict to short-circuit (block the request)

Example:
  runtime.http.middleware("auth.check_request")`,
	},

	"static": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 2); err != nil {
				return err
			}

			path, err := args[0].AsString()
			if err != nil {
				return err
			}

			directory, err := args[1].AsString()
			if err != nil {
				return err
			}

			RuntimeState.Lock()
			RuntimeState.Routes[path] = &RouteInfo{
				Methods:   []string{"GET"},
				Static:    true,
				StaticDir: directory,
			}
			RuntimeState.Unlock()

			return &object.Null{}
		},
		HelpText: `static(path, directory) - Register a static file serving route

Parameters:
  path (string): URL path prefix for static files (e.g., "/assets")
  directory (string): Local directory to serve files from

Example:
  runtime.http.static("/assets", "./public")`,
	},

	"json": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 1); err != nil {
				return err
			}

			statusCode := int64(200)
			var data object.Object = &object.Null{}

			if len(args) >= 2 {
				if code, err := args[0].AsInt(); err == nil {
					statusCode = code
				}
				data = args[1]
			} else {
				data = args[0]
			}

			if c := kwargs.Get("status"); c != nil {
				if code, e := c.AsInt(); e == nil {
					statusCode = code
				}
			}

			return httpResponse(statusCode, map[string]string{
				"Content-Type": "application/json",
			}, data)
		},
		HelpText: `json(status_code, data) - Create a JSON response

Parameters:
  status_code (int): HTTP status code (e.g., 200, 404, 500)
  data: Data to serialize as JSON

Returns:
  dict: Response object for the server

Example:
  return runtime.http.json(200, {"status": "ok"})
  return runtime.http.json(404, {"error": "Not found"})`,
	},

	"redirect": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 1); err != nil {
				return err
			}

			location, err := args[0].AsString()
			if err != nil {
				return err
			}

			statusCode := int64(302)
			if len(args) > 1 {
				if code, e := args[1].AsInt(); e == nil {
					statusCode = code
				}
			}
			if c := kwargs.Get("status"); c != nil {
				if code, e := c.AsInt(); e == nil {
					statusCode = code
				}
			}

			return httpResponse(statusCode, map[string]string{
				"Location": location,
			}, &object.String{Value: ""})
		},
		HelpText: `redirect(location, status=302) - Create a redirect response

Parameters:
  location (string): URL to redirect to
  status (int, optional): HTTP status code (default: 302)

Returns:
  dict: Response object for the server

Example:
  return runtime.http.redirect("/new-location")
  return runtime.http.redirect("/permanent", status=301)`,
	},

	"html": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 1); err != nil {
				return err
			}

			statusCode := int64(200)
			var htmlContent object.Object = &object.String{Value: ""}

			if len(args) >= 2 {
				if code, err := args[0].AsInt(); err == nil {
					statusCode = code
				}
				htmlContent = args[1]
			} else {
				htmlContent = args[0]
			}

			if c := kwargs.Get("status"); c != nil {
				if code, e := c.AsInt(); e == nil {
					statusCode = code
				}
			}

			return httpResponse(statusCode, map[string]string{
				"Content-Type": "text/html; charset=utf-8",
			}, htmlContent)
		},
		HelpText: `html(status_code, content) - Create an HTML response

Parameters:
  status_code (int): HTTP status code
  content (string): HTML content to return

Returns:
  dict: Response object for the server

Example:
  return runtime.http.html(200, "<h1>Hello World</h1>")`,
	},

	"text": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 1); err != nil {
				return err
			}

			statusCode := int64(200)
			var textContent object.Object = &object.String{Value: ""}

			if len(args) >= 2 {
				if code, err := args[0].AsInt(); err == nil {
					statusCode = code
				}
				textContent = args[1]
			} else {
				textContent = args[0]
			}

			if c := kwargs.Get("status"); c != nil {
				if code, e := c.AsInt(); e == nil {
					statusCode = code
				}
			}

			return httpResponse(statusCode, map[string]string{
				"Content-Type": "text/plain; charset=utf-8",
			}, textContent)
		},
		HelpText: `text(status_code, content) - Create a plain text response

Parameters:
  status_code (int): HTTP status code
  content (string): Text content to return

Returns:
  dict: Response object for the server

Example:
  return runtime.http.text(200, "Hello World")`,
	},

	"parse_query": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 1); err != nil {
				return err
			}

			queryString, err := args[0].AsString()
			if err != nil {
				return err
			}

			values, parseErr := url.ParseQuery(queryString)
			if parseErr != nil {
				return errors.NewError("failed to parse query string: %s", parseErr.Error())
			}

			pairs := make(map[string]object.DictPair)
			for key, vals := range values {
				keyObj := &object.String{Value: key}
				dk := object.DictKey(keyObj)
				if len(vals) == 1 {
					pairs[dk] = object.DictPair{
						Key:   keyObj,
						Value: &object.String{Value: vals[0]},
					}
				} else {
					elements := make([]object.Object, len(vals))
					for i, v := range vals {
						elements[i] = &object.String{Value: v}
					}
					pairs[dk] = object.DictPair{
						Key:   keyObj,
						Value: &object.List{Elements: elements},
					}
				}
			}

			return &object.Dict{Pairs: pairs}
		},
		HelpText: `parse_query(query_string) - Parse a URL query string

Parameters:
  query_string (string): Query string to parse (with or without leading ?)

Returns:
  dict: Parsed key-value pairs

Example:
  params = runtime.http.parse_query("name=John&age=30")`,
	},
}, map[string]object.Object{
	"Request": RequestClass,
}, "HTTP server route registration and response helpers")
View Source
var KVSubLibrary = object.NewLibrary("kv", map[string]*object.Builtin{
	"set": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 2); err != nil {
				return err
			}

			key, err := args[0].AsString()
			if err != nil {
				return err
			}

			value, convErr := convertObjectToKVValue(args[1])
			if convErr != nil {
				return convErr
			}

			var ttl int64 = 0
			if t := kwargs.Get("ttl"); t != nil {
				if ttlVal, e := t.AsInt(); e == nil {
					ttl = ttlVal
				}
			} else if len(args) > 2 {
				if ttlVal, e := args[2].AsInt(); e == nil {
					ttl = ttlVal
				}
			}

			entry := &kvEntry{value: value}
			if ttl > 0 {
				entry.expiresAt = time.Now().Add(time.Duration(ttl) * time.Second)
			}

			RuntimeState.Lock()
			RuntimeState.KVData[key] = entry
			RuntimeState.Unlock()

			return &object.Null{}
		},
		HelpText: `set(key, value, ttl=0) - Store a value with optional TTL in seconds

Parameters:
  key (string): The key to store the value under
  value: The value to store (string, int, float, bool, list, dict)
  ttl (int, optional): Time-to-live in seconds. 0 means no expiration.

Example:
  runtime.kv.set("api_key", "secret123")
  runtime.kv.set("session:abc", {"user": "bob"}, ttl=3600)`,
	},

	"get": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 1); err != nil {
				return err
			}

			key, err := args[0].AsString()
			if err != nil {
				return err
			}

			var defaultValue object.Object = &object.Null{}
			if d := kwargs.Get("default"); d != nil {
				defaultValue = d
			} else if len(args) > 1 {
				defaultValue = args[1]
			}

			RuntimeState.RLock()
			entry, exists := RuntimeState.KVData[key]
			RuntimeState.RUnlock()

			if !exists || entry.isExpired() {
				return defaultValue
			}

			return convertKVValueToObject(deepCopy(entry.value))
		},
		HelpText: `get(key, default=None) - Retrieve a value by key

Parameters:
  key (string): The key to retrieve
  default: Value to return if key doesn't exist (default: None)

Returns:
  The stored value (deep copy), or the default if not found

Example:
  value = runtime.kv.get("api_key")
  count = runtime.kv.get("counter", default=0)`,
	},

	"delete": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 1); err != nil {
				return err
			}

			key, err := args[0].AsString()
			if err != nil {
				return err
			}

			RuntimeState.Lock()
			delete(RuntimeState.KVData, key)
			RuntimeState.Unlock()

			return &object.Null{}
		},
		HelpText: `delete(key) - Remove a key from the store

Parameters:
  key (string): The key to delete

Example:
  runtime.kv.delete("session:abc")`,
	},

	"exists": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 1); err != nil {
				return err
			}

			key, err := args[0].AsString()
			if err != nil {
				return err
			}

			RuntimeState.RLock()
			entry, exists := RuntimeState.KVData[key]
			RuntimeState.RUnlock()

			if !exists || entry.isExpired() {
				return &object.Boolean{Value: false}
			}
			return &object.Boolean{Value: true}
		},
		HelpText: `exists(key) - Check if a key exists and is not expired

Parameters:
  key (string): The key to check

Returns:
  bool: True if key exists and is not expired

Example:
  if runtime.kv.exists("config"):
      config = runtime.kv.get("config")`,
	},

	"incr": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 1); err != nil {
				return err
			}

			key, err := args[0].AsString()
			if err != nil {
				return err
			}

			var amount int64 = 1
			if a := kwargs.Get("amount"); a != nil {
				if amt, e := a.AsInt(); e == nil {
					amount = amt
				}
			} else if len(args) > 1 {
				if amt, e := args[1].AsInt(); e == nil {
					amount = amt
				}
			}

			RuntimeState.Lock()
			defer RuntimeState.Unlock()

			entry, exists := RuntimeState.KVData[key]
			if !exists || entry.isExpired() {
				RuntimeState.KVData[key] = &kvEntry{value: amount}
				return object.NewInteger(amount)
			}

			currentVal, ok := entry.value.(int64)
			if !ok {
				return errors.NewError("kv.incr: value is not an integer")
			}

			newVal := currentVal + amount
			entry.value = newVal

			return object.NewInteger(newVal)
		},
		HelpText: `incr(key, amount=1) - Atomically increment an integer value

Parameters:
  key (string): The key to increment
  amount (int, optional): Amount to increment by (default: 1)

Returns:
  int: The new value after incrementing

Example:
  runtime.kv.set("counter", 0)
  runtime.kv.incr("counter")      # returns 1
  runtime.kv.incr("counter", 5)   # returns 6`,
	},

	"ttl": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 1); err != nil {
				return err
			}

			key, err := args[0].AsString()
			if err != nil {
				return err
			}

			RuntimeState.RLock()
			entry, exists := RuntimeState.KVData[key]
			RuntimeState.RUnlock()

			if !exists || entry.isExpired() {
				return object.NewInteger(-2)
			}

			if entry.expiresAt.IsZero() {
				return object.NewInteger(-1)
			}

			remaining := time.Until(entry.expiresAt).Seconds()
			return object.NewInteger(int64(remaining))
		},
		HelpText: `ttl(key) - Get remaining time-to-live for a key

Parameters:
  key (string): The key to check

Returns:
  int: Remaining TTL in seconds, -1 if no expiration, -2 if key doesn't exist

Example:
  runtime.kv.set("session", "data", ttl=3600)
  remaining = runtime.kv.ttl("session")  # e.g., 3599`,
	},

	"keys": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			pattern := "*"
			if p := kwargs.Get("pattern"); p != nil {
				if pat, e := p.AsString(); e == nil {
					pattern = pat
				}
			} else if len(args) > 0 {
				if pat, e := args[0].AsString(); e == nil {
					pattern = pat
				}
			}

			RuntimeState.RLock()
			defer RuntimeState.RUnlock()

			var keys []object.Object
			for key, entry := range RuntimeState.KVData {
				if entry.isExpired() {
					continue
				}

				if pattern == "*" {
					keys = append(keys, &object.String{Value: key})
				} else {
					matched, _ := filepath.Match(pattern, key)
					if matched {
						keys = append(keys, &object.String{Value: key})
					}
				}
			}

			return &object.List{Elements: keys}
		},
		HelpText: `keys(pattern="*") - Get all keys matching a glob pattern

Parameters:
  pattern (string, optional): Glob pattern to match keys (default: "*")

Returns:
  list: List of matching keys

Example:
  all_keys = runtime.kv.keys()
  user_keys = runtime.kv.keys("user:*")
  session_keys = runtime.kv.keys("session:*")`,
	},

	"clear": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			RuntimeState.Lock()
			RuntimeState.KVData = make(map[string]*kvEntry)
			RuntimeState.Unlock()

			return &object.Null{}
		},
		HelpText: `clear() - Remove all keys from the store

Warning: This operation cannot be undone.

Example:
  runtime.kv.clear()`,
	},
}, nil, "Thread-safe key-value store for sharing state across requests.\n\nNote: The KV store is in-memory with no size limits. Keys without a TTL persist\nindefinitely. Use TTLs and periodic cleanup to avoid unbounded memory growth.\nExpired entries are cleaned up automatically every 60 seconds.")
View Source
var RequestClass = &object.Class{
	Name: "Request",
	Methods: map[string]object.Object{
		"json": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 1); err != nil {
					return err
				}
				instance, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewError("json() called on non-Request object")
				}

				body, err := instance.Fields["body"].AsString()
				if err != nil {
					return err
				}

				if body == "" {
					return &object.Null{}
				}

				return conversion.MustParseJSON(body)
			},
			HelpText: `json() - Parse request body as JSON

Returns the parsed JSON as a dict or list, or None if body is empty.`,
		},
	},
}

RequestClass is the class for Request objects passed to handlers

View Source
var RequestsLibrary = object.NewLibrary(RequestsLibraryName, map[string]*object.Builtin{

	"exceptions": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			return exceptionsNamespace
		},
	},
	"get": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			url, _, options, err := extractRequestArgs(kwargs, args, false)
			if err != nil {
				return err
			}
			timeout, headers, user, pass := parseRequestOptions(options)
			return httpRequestWithContext(ctx, "GET", url, "", timeout, headers, user, pass)
		},
		HelpText: `get(url, **kwargs) - Send a GET request

Sends an HTTP GET request to the specified URL.

Parameters:
  url (string): The URL to send the request to
  **kwargs: Optional arguments
    - timeout (int): Request timeout in seconds (default: 5)
    - headers (dict): HTTP headers as key-value pairs
    - auth (tuple/list): Basic authentication as (username, password)

Returns:
  Response object with status_code, text, headers, body, url, and json() method`,
	},
	"post": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			url, data, options, err := extractRequestArgs(kwargs, args, true)
			if err != nil {
				return err
			}
			timeout, headers, user, pass := parseRequestOptions(options)
			return httpRequestWithContext(ctx, "POST", url, data, timeout, headers, user, pass)
		},
		HelpText: `post(url, data=None, json=None, **kwargs) - Send a POST request

Sends an HTTP POST request to the specified URL with the given data.

Parameters:
  url (string): The URL to send the request to
  data (string, optional): The request body data as a string
  json (dict/list, optional): Data to be JSON-encoded and sent (sets Content-Type to application/json)
  **kwargs: Optional arguments
    - timeout (int): Request timeout in seconds (default: 5)
    - headers (dict): HTTP headers as key-value pairs
    - auth (tuple/list): Basic authentication as (username, password)

Note: Use either 'data' or 'json', not both.

Returns:
  Response object with status_code, text, headers, body, url, and json() method`,
	},
	"put": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			url, data, options, err := extractRequestArgs(kwargs, args, true)
			if err != nil {
				return err
			}
			timeout, headers, user, pass := parseRequestOptions(options)
			return httpRequestWithContext(ctx, "PUT", url, data, timeout, headers, user, pass)
		},
		HelpText: `put(url, data=None, json=None, **kwargs) - Send a PUT request

Sends an HTTP PUT request to the specified URL with the given data.

Parameters:
  url (string): The URL to send the request to
  data (string, optional): The request body data as a string
  json (dict/list, optional): Data to be JSON-encoded and sent (sets Content-Type to application/json)
  **kwargs: Optional arguments
    - timeout (int): Request timeout in seconds (default: 5)
    - headers (dict): HTTP headers as key-value pairs
    - auth (tuple/list): Basic authentication as (username, password)

Note: Use either 'data' or 'json', not both.

Returns:
  Response object with status_code, text, headers, body, url, and json() method`,
	},
	"delete": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			url, _, options, err := extractRequestArgs(kwargs, args, false)
			if err != nil {
				return err
			}
			timeout, headers, user, pass := parseRequestOptions(options)
			return httpRequestWithContext(ctx, "DELETE", url, "", timeout, headers, user, pass)
		},
		HelpText: `delete(url, **kwargs) - Send a DELETE request

Sends an HTTP DELETE request to the specified URL.

Parameters:
  url (string): The URL to send the request to
  **kwargs: Optional arguments
    - timeout (int): Request timeout in seconds (default: 5)
    - headers (dict): HTTP headers as key-value pairs
    - auth (tuple/list): Basic authentication as (username, password)

Returns:
  Response object with status_code, text, headers, body, url, and json() method`,
	},
	"patch": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			url, data, options, err := extractRequestArgs(kwargs, args, true)
			if err != nil {
				return err
			}
			timeout, headers, user, pass := parseRequestOptions(options)
			return httpRequestWithContext(ctx, "PATCH", url, data, timeout, headers, user, pass)
		},
		HelpText: `patch(url, data=None, json=None, **kwargs) - Send a PATCH request

Sends an HTTP PATCH request to the specified URL with the given data.

Parameters:
  url (string): The URL to send the request to
  data (string, optional): The request body data as a string
  json (dict/list, optional): Data to be JSON-encoded and sent (sets Content-Type to application/json)
  **kwargs: Optional arguments
    - timeout (int): Request timeout in seconds (default: 5)
    - headers (dict): HTTP headers as key-value pairs
    - auth (tuple/list): Basic authentication as (username, password)

Note: Use either 'data' or 'json', not both.

Returns:
  Response object with status_code, text, headers, body, url, and json() method`,
	},
}, map[string]object.Object{

	"RequestException": requestExceptionType,
	"HTTPError":        httpErrorType,
	"Response":         ResponseClass,
}, "HTTP requests library")
View Source
var ResponseClass = &object.Class{
	Name: "Response",
	Methods: map[string]object.Object{
		"json": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 1); err != nil {
					return err
				}
				if instance, ok := args[0].(*object.Instance); ok {
					if body, err := instance.Fields["body"].AsString(); err == nil {
						return conversion.MustParseJSON(body)
					}
				}
				return errors.NewError("json() called on non-Response object")
			},
			HelpText: `json() - Parses the response body as JSON and returns the parsed object`,
		},
		"raise_for_status": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 1); err != nil {
					return err
				}
				if instance, ok := args[0].(*object.Instance); ok {
					if statusCode, err := instance.Fields["status_code"].AsInt(); err == nil {
						if statusCode >= 400 {
							kind := "Client"
							if statusCode >= 500 {
								kind = "Server"
							}
							return &object.Exception{
								ExceptionType: "HTTPError",
								Message:       fmt.Sprintf("HTTPError: %d %s Error", statusCode, kind),
							}
						}
						return &object.Null{}
					}
				}
				return errors.NewError("raise_for_status() called on non-Response object")
			},
			HelpText: `raise_for_status() - Raises an exception if the status code indicates an error`,
		},
	},
}

ResponseClass defines the Response class with its methods

View Source
var RuntimeLibrary = RuntimeLibraryWithSubs

RuntimeLibrary is an alias for RuntimeLibraryWithSubs for backward compatibility

View Source
var RuntimeLibraryCore = object.NewLibrary(RuntimeLibraryName, RuntimeLibraryFunctions, nil, "Runtime library for background tasks")

RuntimeLibraryCore is the runtime library without sub-libraries

View Source
var RuntimeLibraryFunctions = map[string]*object.Builtin{
	"background": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 2); err != nil {
				return err
			}

			name, err := args[0].AsString()
			if err != nil {
				return err
			}

			handler, err := args[1].AsString()
			if err != nil {
				return err
			}

			fnArgs := args[2:]
			fnKwargs := kwargs.Kwargs
			env := getEnvFromContext(ctx)
			eval := evaliface.FromContext(ctx)

			RuntimeState.Lock()
			RuntimeState.Backgrounds[name] = handler
			RuntimeState.BackgroundArgs[name] = fnArgs
			RuntimeState.BackgroundKwargs[name] = fnKwargs
			RuntimeState.BackgroundEnvs[name] = env
			RuntimeState.BackgroundEvals[name] = eval
			RuntimeState.BackgroundCtxs[name] = ctx
			backgroundReady := RuntimeState.BackgroundReady
			factory := RuntimeState.BackgroundFactory
			RuntimeState.Unlock()

			if backgroundReady {
				return startBackgroundTask(name, handler, fnArgs, fnKwargs, env, eval, factory, ctx)
			}

			return &object.Null{}
		},
		HelpText: `background(name, handler, *args, **kwargs) - Register and start a background task

Registers a background task and starts it immediately in a goroutine (unless in server mode).
Returns a Promise object that can be used to wait for completion or get the result.

Parameters:
  name (string): Unique name for the background task
  handler (string): Function name to execute
  *args: Positional arguments to pass to the function
  **kwargs: Keyword arguments to pass to the function

Returns:
  Promise object (in script mode) or null (in server mode)

Example:
  def my_task(x, y, operation="add"):
      if operation == "add":
          return x + y
      return x * y

  promise = runtime.background("calc", "my_task", 10, 5, operation="multiply")
  if promise:
      result = promise.get()  # Returns 50`,
	},
}

RuntimeLibraryFunctions contains the core runtime functions (background)

View Source
var RuntimeLibraryWithSubs = NewRuntimeLibraryWithSubs(nil)

RuntimeLibraryWithSubs is the runtime library with all sub-libraries (http, kv, sync, sandbox) Note: This uses nil for sandbox allowed paths (no restrictions). For custom sandbox paths, use NewRuntimeLibraryWithSubs(allowedPaths).

View Source
var RuntimeState = struct {
	sync.RWMutex

	// HTTP routes
	Routes     map[string]*RouteInfo
	Middleware string

	// Background tasks
	Backgrounds       map[string]string                   // name -> "function_name"
	BackgroundArgs    map[string][]object.Object          // name -> args
	BackgroundKwargs  map[string]map[string]object.Object // name -> kwargs
	BackgroundEnvs    map[string]*object.Environment      // name -> environment
	BackgroundEvals   map[string]evaliface.Evaluator      // name -> evaluator
	BackgroundFactory SandboxFactory                      // Factory to create new Scriptling instances
	BackgroundCtxs    map[string]context.Context          // name -> context
	BackgroundReady   bool                                // If true, start tasks immediately

	// KV store
	KVData map[string]*kvEntry

	// Sync primitives
	WaitGroups map[string]*RuntimeWaitGroup
	Queues     map[string]*RuntimeQueue
	Atomics    map[string]*RuntimeAtomic
	Shareds    map[string]*RuntimeShared
}{
	Routes:            make(map[string]*RouteInfo),
	Backgrounds:       make(map[string]string),
	BackgroundArgs:    make(map[string][]object.Object),
	BackgroundKwargs:  make(map[string]map[string]object.Object),
	BackgroundEnvs:    make(map[string]*object.Environment),
	BackgroundEvals:   make(map[string]evaliface.Evaluator),
	BackgroundFactory: nil,
	BackgroundCtxs:    make(map[string]context.Context),
	BackgroundReady:   false,
	KVData:            make(map[string]*kvEntry),
	WaitGroups:        make(map[string]*RuntimeWaitGroup),
	Queues:            make(map[string]*RuntimeQueue),
	Atomics:           make(map[string]*RuntimeAtomic),
	Shareds:           make(map[string]*RuntimeShared),
}

RuntimeState holds all runtime state

View Source
var SecretsLibrary = object.NewLibrary(SecretsLibraryName, map[string]*object.Builtin{
	"token_bytes": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			nbytes := 32
			if len(args) > 0 {
				if intVal, ok := args[0].(*object.Integer); ok {
					nbytes = int(intVal.Value)
				}
			}

			if nbytes < 1 {
				return errors.NewError("token_bytes requires a positive number of bytes")
			}

			bytes := make([]byte, nbytes)
			_, err := rand.Read(bytes)
			if err != nil {
				return errors.NewError("failed to generate random bytes: %s", err.Error())
			}

			elements := make([]object.Object, nbytes)
			for i, b := range bytes {
				elements[i] = object.NewInteger(int64(b))
			}
			return &object.List{Elements: elements}
		},
		HelpText: `token_bytes([nbytes]) - Generate nbytes random bytes

Parameters:
  nbytes - Number of bytes to generate (default 32)

Returns: List of integers representing bytes

Example:
  import secrets
  bytes = secrets.token_bytes(16)`,
	},

	"token_hex": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			nbytes := 32
			if len(args) > 0 {
				if intVal, ok := args[0].(*object.Integer); ok {
					nbytes = int(intVal.Value)
				}
			}

			if nbytes < 1 {
				return errors.NewError("token_hex requires a positive number of bytes")
			}

			bytes := make([]byte, nbytes)
			_, err := rand.Read(bytes)
			if err != nil {
				return errors.NewError("failed to generate random bytes: %s", err.Error())
			}

			return &object.String{Value: hex.EncodeToString(bytes)}
		},
		HelpText: `token_hex([nbytes]) - Generate random text in hexadecimal

Parameters:
  nbytes - Number of random bytes (string will be 2x this length) (default 32)

Returns: Hex string

Example:
  import secrets
  token = secrets.token_hex(16)  # 32 character hex string`,
	},

	"token_urlsafe": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			nbytes := 32
			if len(args) > 0 {
				if intVal, ok := args[0].(*object.Integer); ok {
					nbytes = int(intVal.Value)
				}
			}

			if nbytes < 1 {
				return errors.NewError("token_urlsafe requires a positive number of bytes")
			}

			bytes := make([]byte, nbytes)
			_, err := rand.Read(bytes)
			if err != nil {
				return errors.NewError("failed to generate random bytes: %s", err.Error())
			}

			encoded := base64.URLEncoding.EncodeToString(bytes)

			encoded = strings.TrimRight(encoded, "=")
			return &object.String{Value: encoded}
		},
		HelpText: `token_urlsafe([nbytes]) - Generate URL-safe random text

Parameters:
  nbytes - Number of random bytes (default 32)

Returns: URL-safe base64 encoded string

Example:
  import secrets
  token = secrets.token_urlsafe(16)`,
	},

	"randbelow": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if len(args) != 1 {
				return errors.NewError("randbelow() requires exactly 1 argument")
			}

			n, ok := args[0].(*object.Integer)
			if !ok {
				return errors.NewTypeError("INTEGER", args[0].Type().String())
			}

			if n.Value <= 0 {
				return errors.NewError("randbelow requires a positive upper bound")
			}

			result, err := rand.Int(rand.Reader, big.NewInt(n.Value))
			if err != nil {
				return errors.NewError("failed to generate random number: %s", err.Error())
			}

			return object.NewInteger(result.Int64())
		},
		HelpText: `randbelow(n) - Generate a random integer in range [0, n)

Parameters:
  n - Exclusive upper bound (must be positive)

Returns: Random integer from 0 to n-1

Example:
  import secrets
  dice = secrets.randbelow(6) + 1  # 1-6`,
	},

	"randbits": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if len(args) != 1 {
				return errors.NewError("randbits() requires exactly 1 argument")
			}

			k, ok := args[0].(*object.Integer)
			if !ok {
				return errors.NewTypeError("INTEGER", args[0].Type().String())
			}

			if k.Value < 1 {
				return errors.NewError("randbits requires a positive number of bits")
			}

			result, err := rand.Int(rand.Reader, big.NewInt(0).Lsh(big.NewInt(1), uint(k.Value)))
			if err != nil {
				return errors.NewError("failed to generate random bits: %s", err.Error())
			}

			return object.NewInteger(result.Int64())
		},
		HelpText: `randbits(k) - Generate a random integer with k random bits

Parameters:
  k - Number of random bits (must be positive)

Returns: Random integer with k bits

Example:
  import secrets
  random_int = secrets.randbits(8)  # 0-255`,
	},

	"choice": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if len(args) != 1 {
				return errors.NewError("choice() requires exactly 1 argument")
			}

			if str, ok := args[0].(*object.String); ok {
				if len(str.Value) == 0 {
					return errors.NewError("cannot choose from empty sequence")
				}
				idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(str.Value))))
				if err != nil {
					return errors.NewError("failed to generate random index: %s", err.Error())
				}
				return &object.String{Value: string(str.Value[idx.Int64()])}
			}

			list, ok := args[0].(*object.List)
			if !ok {
				return errors.NewTypeError("LIST or STRING", args[0].Type().String())
			}

			if len(list.Elements) == 0 {
				return errors.NewError("cannot choose from empty sequence")
			}

			idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(list.Elements))))
			if err != nil {
				return errors.NewError("failed to generate random index: %s", err.Error())
			}

			return list.Elements[idx.Int64()]
		},
		HelpText: `choice(sequence) - Return a random element from sequence

Parameters:
  sequence - Non-empty list or string to choose from

Returns: Random element from the sequence

Example:
  import secrets
  item = secrets.choice(["apple", "banana", "cherry"])
  char = secrets.choice("abcdef")`,
	},

	"compare_digest": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if len(args) != 2 {
				return errors.NewError("compare_digest() requires exactly 2 arguments")
			}

			a, okA := args[0].(*object.String)
			b, okB := args[1].(*object.String)
			if !okA || !okB {
				return errors.NewError("compare_digest() requires two string arguments")
			}

			if subtle.ConstantTimeCompare([]byte(a.Value), []byte(b.Value)) == 1 {
				return &object.Boolean{Value: true}
			}
			return &object.Boolean{Value: false}
		},
		HelpText: `compare_digest(a, b) - Compare two strings using constant-time comparison

This function is designed to prevent timing attacks when comparing secret values.

Parameters:
  a - First string
  b - Second string

Returns: True if strings are equal, False otherwise

Example:
  import secrets
  secrets.compare_digest(user_token, stored_token)`,
	},
}, nil, "Cryptographically strong random number generation (extended library)")

SecretsLibrary provides cryptographically strong random number generation NOTE: This is an extended library and not enabled by default

View Source
var SubprocessLibrary = object.NewLibrary(SubprocessLibraryName, map[string]*object.Builtin{
	"run": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 1); err != nil {
				return err
			}

			// Parse args - can be string or list
			var cmdArgs []string
			var cmdStr string
			if args[0].Type() == object.STRING_OBJ {
				cmdStr, _ = args[0].AsString()

				shell := false
				if sh, exists := kwargs.Kwargs["shell"]; exists {
					if b, ok := sh.(*object.Boolean); ok {
						shell = b.Value
					}
				}
				if shell {

					cmdArgs = []string{cmdStr}
				} else {

					cmdArgs = strings.Fields(cmdStr)
				}
			} else if args[0].Type() == object.LIST_OBJ {
				list, _ := args[0].AsList()
				cmdArgs = make([]string, len(list))
				for i, arg := range list {
					if str, err := arg.AsString(); err == nil {
						cmdArgs[i] = str
					} else {
						return errors.NewTypeError("STRING", arg.Type().String())
					}
				}
			} else {
				return errors.NewTypeError("STRING or LIST", args[0].Type().String())
			}

			captureOutput := false
			shell := false
			cwd := ""
			timeout := 0.0
			check := false
			text := false
			encoding := "utf-8"
			inputData := ""
			env := make(map[string]string)

			if capture, exists := kwargs.Kwargs["capture_output"]; exists {
				if b, ok := capture.(*object.Boolean); ok {
					captureOutput = b.Value
				}
			}
			if sh, exists := kwargs.Kwargs["shell"]; exists {
				if b, ok := sh.(*object.Boolean); ok {
					shell = b.Value
				}
			}
			if wd, exists := kwargs.Kwargs["cwd"]; exists {
				if s, ok := wd.(*object.String); ok {
					cwd = s.Value
				}
			}
			if to, exists := kwargs.Kwargs["timeout"]; exists {
				if f, ok := to.(*object.Float); ok {
					timeout = f.Value
				} else if i, ok := to.(*object.Integer); ok {
					timeout = float64(i.Value)
				}
			}
			if ch, exists := kwargs.Kwargs["check"]; exists {
				if b, ok := ch.(*object.Boolean); ok {
					check = b.Value
				}
			}
			if txt, exists := kwargs.Kwargs["text"]; exists {
				if b, ok := txt.(*object.Boolean); ok {
					text = b.Value
				}
			}
			if enc, exists := kwargs.Kwargs["encoding"]; exists {
				if s, ok := enc.(*object.String); ok {
					encoding = s.Value
				}
			}
			if inp, exists := kwargs.Kwargs["input"]; exists {
				if s, ok := inp.(*object.String); ok {
					inputData = s.Value
				}
			}
			if envDict, exists := kwargs.Kwargs["env"]; exists {
				if d, ok := envDict.(*object.Dict); ok {
					for _, pair := range d.Pairs {
						if valStr, ok := pair.Value.(*object.String); ok {
							env[pair.StringKey()] = valStr.Value
						}
					}
				}
			}

			if shell && args[0].Type() == object.STRING_OBJ {
				cmdArgs = []string{"sh", "-c", cmdStr}
			}

			// Execute command
			var cmd *exec.Cmd
			if shell && args[0].Type() == object.STRING_OBJ {
				cmd = exec.Command(cmdArgs[0], cmdArgs[1:]...)
			} else {
				cmd = exec.Command(cmdArgs[0], cmdArgs[1:]...)
			}

			if cwd != "" {
				cmd.Dir = cwd
			}

			if len(env) > 0 {
				cmd.Env = make([]string, 0, len(env))
				for k, v := range env {
					cmd.Env = append(cmd.Env, k+"="+v)
				}
			}

			if inputData != "" {
				cmd.Stdin = strings.NewReader(inputData)
			}

			if timeout > 0 {
				ctx, cancel := context.WithTimeout(ctx, time.Duration(timeout*float64(time.Second)))
				defer cancel()
				cmd = exec.CommandContext(ctx, cmd.Path, cmd.Args[1:]...)
				cmd.Dir = cwd
				if len(env) > 0 {
					cmd.Env = make([]string, 0, len(env))
					for k, v := range env {
						cmd.Env = append(cmd.Env, k+"="+v)
					}
				}
				if inputData != "" {
					cmd.Stdin = strings.NewReader(inputData)
				}
			}

			var stdout, stderr []byte
			var err error

			if captureOutput {
				stdout, err = cmd.Output()
				if exitErr, ok := err.(*exec.ExitError); ok {
					stderr = exitErr.Stderr
				}
			} else {
				err = cmd.Run()
			}

			returncode := 0
			if err != nil {
				if exitErr, ok := err.(*exec.ExitError); ok {
					returncode = exitErr.ExitCode()
				} else {
					return errors.NewError("Command execution failed: %v", err)
				}
			}

			// Convert output based on text/encoding settings
			var stdoutStr, stderrStr string
			if text {

				_ = encoding
				stdoutStr = string(stdout)
				stderrStr = string(stderr)
			} else {

				stdoutStr = string(stdout)
				stderrStr = string(stderr)
			}
			instance := &object.Instance{
				Class: CompletedProcessClass,
				Fields: map[string]object.Object{
					"args":       &object.List{Elements: make([]object.Object, len(cmdArgs))},
					"returncode": &object.Integer{Value: int64(returncode)},
					"stdout":     &object.String{Value: stdoutStr},
					"stderr":     &object.String{Value: stderrStr},
				},
			}
			for i, arg := range cmdArgs {
				instance.Fields["args"].(*object.List).Elements[i] = &object.String{Value: arg}
			}

			if check && returncode != 0 {
				return errors.NewError("Command returned non-zero exit status %d", returncode)
			}

			return instance
		},
		HelpText: `run(args, options={}) - Run a command

Runs a command and returns a CompletedProcess instance.

Parameters:
  args (string or list): Command to run. If string, split on spaces. If list, each element is an argument.
  options (dict, optional): Options
    - capture_output (bool): Capture stdout and stderr (default: false)
    - shell (bool): Run command through shell (default: false)
    - cwd (string): Working directory for command
    - timeout (int): Timeout in seconds
    - check (bool): Raise exception if returncode is non-zero

Returns:
  CompletedProcess instance with args, returncode, stdout, stderr`,
	},
}, map[string]object.Object{}, "Subprocess library for running external commands")
View Source
var SyncSubLibrary = object.NewLibrary("sync", map[string]*object.Builtin{
	"WaitGroup": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 1); err != nil {
				return err
			}

			name, err := args[0].AsString()
			if err != nil {
				return err
			}

			RuntimeState.Lock()
			wg, exists := RuntimeState.WaitGroups[name]
			if !exists {
				wg = &RuntimeWaitGroup{}
				RuntimeState.WaitGroups[name] = wg
			}
			RuntimeState.Unlock()

			return &object.Builtin{
				Attributes: map[string]object.Object{
					"add": &object.Builtin{
						Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
							delta := int64(1)
							if len(args) > 0 {
								if d, err := args[0].AsInt(); err == nil {
									delta = d
								}
							}
							wg.wg.Add(int(delta))
							return &object.Null{}
						},
						HelpText: "add(delta=1) - Add to the wait group counter",
					},
					"done": &object.Builtin{
						Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
							wg.wg.Done()
							return &object.Null{}
						},
						HelpText: "done() - Decrement the wait group counter",
					},
					"wait": &object.Builtin{
						Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
							wg.wg.Wait()
							return &object.Null{}
						},
						HelpText: "wait() - Block until counter reaches zero",
					},
				},
				HelpText: "WaitGroup - Go-style synchronization primitive",
			}
		},
		HelpText: `WaitGroup(name) - Get or create a named wait group

Parameters:
  name (string): Unique name for the wait group (shared across environments)

Example:
    wg = runtime.sync.WaitGroup("tasks")

    def worker(id):
        print(f"Worker {id}")
        wg.done()

    for i in range(10):
        wg.add(1)
        runtime.run(worker, i)

    wg.wait()`,
	},

	"Queue": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 1); err != nil {
				return err
			}

			name, err := args[0].AsString()
			if err != nil {
				return err
			}

			maxsize := 0
			if len(args) > 1 {
				if m, err := args[1].AsInt(); err == nil {
					maxsize = int(m)
				}
			}
			if m, ok := kwargs.Kwargs["maxsize"]; ok {
				if mInt, err := m.AsInt(); err == nil {
					maxsize = int(mInt)
				}
			}

			RuntimeState.Lock()
			queue, exists := RuntimeState.Queues[name]
			if !exists {
				queue = newRuntimeQueue(maxsize)
				RuntimeState.Queues[name] = queue
			}
			RuntimeState.Unlock()

			return &object.Builtin{
				Attributes: map[string]object.Object{
					"put": &object.Builtin{
						Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
							if err := errors.ExactArgs(args, 1); err != nil {
								return err
							}
							if err := queue.put(ctx, args[0]); err != nil {
								return errors.NewError("queue error: %v", err)
							}
							return &object.Null{}
						},
						HelpText: "put(item) - Add item to queue (blocks if full, respects context timeout)",
					},
					"get": &object.Builtin{
						Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
							item, err := queue.get(ctx)
							if err != nil {
								return errors.NewError("queue error: %v", err)
							}
							return item
						},
						HelpText: "get() - Remove and return item from queue (blocks if empty, respects context timeout)",
					},
					"size": &object.Builtin{
						Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
							return object.NewInteger(int64(queue.size()))
						},
						HelpText: "size() - Return number of items in queue",
					},
					"close": &object.Builtin{
						Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
							queue.close()
							return &object.Null{}
						},
						HelpText: "close() - Close the queue",
					},
				},
				HelpText: "Queue - Thread-safe queue for producer-consumer patterns",
			}
		},
		HelpText: `Queue(name, maxsize=0) - Get or create a named queue

Parameters:
  name (string): Unique name for the queue (shared across environments)
  maxsize (int): Maximum queue size (0 = unbounded)

Example:
    queue = runtime.sync.Queue("jobs", maxsize=100)

    def producer():
        for i in range(10):
            queue.put(i)

    def consumer():
        for i in range(10):
            item = queue.get()
            print(item)

    runtime.run(producer)
    runtime.run(consumer)`,
	},

	"Atomic": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 1); err != nil {
				return err
			}

			name, err := args[0].AsString()
			if err != nil {
				return err
			}

			initial := int64(0)
			if len(args) > 1 {
				if i, err := args[1].AsInt(); err == nil {
					initial = i
				}
			}
			if i := kwargs.Get("initial"); i != nil {
				if iVal, err := i.AsInt(); err == nil {
					initial = iVal
				}
			}

			RuntimeState.Lock()
			atomic, exists := RuntimeState.Atomics[name]
			if !exists {
				atomic = &RuntimeAtomic{value: initial}
				RuntimeState.Atomics[name] = atomic
			}
			RuntimeState.Unlock()

			return &object.Builtin{
				Attributes: map[string]object.Object{
					"add": &object.Builtin{
						Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
							delta := int64(1)
							if len(args) > 0 {
								if d, err := args[0].AsInt(); err == nil {
									delta = d
								} else {
									return errors.NewTypeError("INTEGER", args[0].Type().String())
								}
							}
							newVal := atomic.add(delta)
							return object.NewInteger(newVal)
						},
						HelpText: "add(delta=1) - Atomically add delta and return new value",
					},
					"get": &object.Builtin{
						Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
							return object.NewInteger(atomic.get())
						},
						HelpText: "get() - Atomically read the value",
					},
					"set": &object.Builtin{
						Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
							if err := errors.ExactArgs(args, 1); err != nil {
								return err
							}
							if val, err := args[0].AsInt(); err == nil {
								atomic.set(val)
								return &object.Null{}
							}
							return errors.NewTypeError("INTEGER", args[0].Type().String())
						},
						HelpText: "set(value) - Atomically set the value",
					},
				},
				HelpText: "Atomic integer - lock-free operations",
			}
		},
		HelpText: `Atomic(name, initial=0) - Get or create a named atomic counter

Parameters:
  name (string): Unique name for the counter (shared across environments)
  initial (int): Initial value (only used if creating new counter)

Example:
    counter = runtime.sync.Atomic("requests", initial=0)
    counter.add(1)      # Atomic increment
    counter.add(-5)     # Atomic add
    counter.set(100)    # Atomic set
    value = counter.get()  # Atomic read`,
	},

	"Shared": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.MinArgs(args, 1); err != nil {
				return err
			}

			name, err := args[0].AsString()
			if err != nil {
				return err
			}

			var initial object.Object = &object.Null{}
			if len(args) > 1 {
				initial = args[1]
			}
			if i := kwargs.Get("initial"); i != nil {
				initial = i
			}

			RuntimeState.Lock()
			shared, exists := RuntimeState.Shareds[name]
			if !exists {
				shared = &RuntimeShared{value: initial}
				RuntimeState.Shareds[name] = shared
			}
			RuntimeState.Unlock()

			return &object.Builtin{
				Attributes: map[string]object.Object{
					"get": &object.Builtin{
						Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
							return shared.get()
						},
						HelpText: "get() - Get the current value (thread-safe read)",
					},
					"set": &object.Builtin{
						Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
							if err := errors.ExactArgs(args, 1); err != nil {
								return err
							}
							shared.set(args[0])
							return &object.Null{}
						},
						HelpText: "set(value) - Set the value (thread-safe write)",
					},
					"update": &object.Builtin{
						Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
							if err := errors.ExactArgs(args, 1); err != nil {
								return err
							}
							fn := args[0]
							result := shared.update(func(current object.Object) object.Object {
								eval := evaliface.FromContext(ctx)
								if eval == nil {
									return current
								}
								env := getEnvFromContext(ctx)
								return eval.CallObjectFunction(ctx, fn, []object.Object{current}, nil, env)
							})
							return result
						},
						HelpText: "update(fn) - Atomically read-modify-write: fn receives current value, returns new value",
					},
				},
				HelpText: "Shared variable - thread-safe access with get()/set()/update()",
			}
		},
		HelpText: `Shared(name, initial) - Get or create a named shared variable

Parameters:
  name (string): Unique name for the variable (shared across environments)
  initial: Initial value (only used if creating new variable)

Note: Values should be treated as immutable. Use set() to replace, or
update() for atomic read-modify-write operations.

Example:
    counter = runtime.sync.Shared("counter", 0)

    def increment(current):
        return current + 1

    # Atomic increment using update()
    counter.update(increment)

    # Simple get/set for immutable values
    counter.set(42)
    value = counter.get()`,
	},
}, nil, "Cross-environment named concurrency primitives")
View Source
var TOMLLibrary = object.NewLibrary(TOMLLibraryName, map[string]*object.Builtin{
	"loads": {
		Fn: tomlLoadsFunc,
		HelpText: `loads(toml_string) - Parse TOML string

Parses a TOML string and returns the corresponding Scriptling object.

This function is compatible with Python's tomllib.loads() from Python 3.11+.

Example:
    import toml
    data = toml.loads("[database]\nhost = \"localhost\"\nport = 5432")
    print(data["database"]["host"])`,
	},
	"dumps": {
		Fn: tomlDumpsFunc,
		HelpText: `dumps(obj) - Convert Scriptling object to TOML string

Converts a Scriptling object to a TOML formatted string.

Note: Python's tomllib does not include a write function. This follows
the convention of the tomli-w library which provides dumps().

Example:
    import toml
    data = {"database": {"host": "localhost", "port": 5432}}
    toml_str = toml.dumps(data)
    print(toml_str)`,
	},
}, nil, "TOML parsing and generation")

TOMLLibrary provides TOML parsing and generation functionality

View Source
var WaitForLibrary = object.NewLibrary(WaitForLibraryName,
	map[string]*object.Builtin{
		"file": {
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.MinArgs(args, 1); err != nil {
					return err
				}

				path, err := args[0].AsString()
				if err != nil {
					return err
				}

				timeout, pollRate, err := parseWaitOptions(args, kwargs.Kwargs)
				if err != nil {
					return err
				}

				deadline := time.Now().Add(time.Duration(timeout) * time.Second)
				pollInterval := time.Duration(pollRate * float64(time.Second))

				for time.Now().Before(deadline) {
					if _, err := os.Stat(path); err == nil {
						return &object.Boolean{Value: true}
					}

					select {
					case <-ctx.Done():
						return &object.Boolean{Value: false}
					case <-time.After(pollInterval):

					}
				}

				if _, err := os.Stat(path); err == nil {
					return &object.Boolean{Value: true}
				}
				return &object.Boolean{Value: false}
			},
			HelpText: `file(path, timeout=30, poll_rate=1) - Wait for a file to exist

Waits for the specified file to become available.

Parameters:
  path (string): Path to the file to wait for
  timeout (int): Maximum time to wait in seconds (default: 30)
  poll_rate (float): Time between checks in seconds (default: 1)

Returns:
  bool: True if file exists, False if timeout exceeded`,
		},
		"dir": {
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.MinArgs(args, 1); err != nil {
					return err
				}

				path, err := args[0].AsString()
				if err != nil {
					return err
				}

				timeout, pollRate, err := parseWaitOptions(args, kwargs.Kwargs)
				if err != nil {
					return err
				}

				deadline := time.Now().Add(time.Duration(timeout) * time.Second)
				pollInterval := time.Duration(pollRate * float64(time.Second))

				for time.Now().Before(deadline) {
					if info, err := os.Stat(path); err == nil {
						if info.IsDir() {
							return &object.Boolean{Value: true}
						}
					}

					select {
					case <-ctx.Done():
						return &object.Boolean{Value: false}
					case <-time.After(pollInterval):

					}
				}

				if info, err := os.Stat(path); err == nil {
					if info.IsDir() {
						return &object.Boolean{Value: true}
					}
				}
				return &object.Boolean{Value: false}
			},
			HelpText: `dir(path, timeout=30, poll_rate=1) - Wait for a directory to exist

Waits for the specified directory to become available.

Parameters:
  path (string): Path to the directory to wait for
  timeout (int): Maximum time to wait in seconds (default: 30)
  poll_rate (float): Time between checks in seconds (default: 1)

Returns:
  bool: True if directory exists, False if timeout exceeded`,
		},
		"port": {
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.MinArgs(args, 2); err != nil {
					return err
				}

				host, err := args[0].AsString()
				if err != nil {
					return err
				}

				var port int
				switch v := args[1].(type) {
				case *object.Integer:
					port = int(v.Value)
				case *object.String:
					p, err := strconv.Atoi(v.Value)
					if err != nil {
						return errors.NewError("invalid port number: %s", v.Value)
					}
					port = p
				default:
					return errors.NewTypeError("INT|STRING", args[1].Type().String())
				}

				timeout, pollRate, err := parseWaitOptionsKwargsOnly(30, 1.0, kwargs.Kwargs)
				if err != nil {
					return err
				}

				deadline := time.Now().Add(time.Duration(timeout) * time.Second)
				pollInterval := time.Duration(pollRate * float64(time.Second))
				address := fmt.Sprintf("%s:%d", host, port)

				for time.Now().Before(deadline) {
					conn, err := net.DialTimeout("tcp", address, time.Second)
					if err == nil {
						conn.Close()
						return &object.Boolean{Value: true}
					}

					select {
					case <-ctx.Done():
						return &object.Boolean{Value: false}
					case <-time.After(pollInterval):

					}
				}

				if conn, err := net.DialTimeout("tcp", address, time.Second); err == nil {
					conn.Close()
					return &object.Boolean{Value: true}
				}
				return &object.Boolean{Value: false}
			},
			HelpText: `port(host, port, timeout=30, poll_rate=1) - Wait for a TCP port to be open

Waits for the specified TCP port to accept connections.

Parameters:
  host (string): Hostname or IP address
  port (int|string): Port number
  timeout (int): Maximum time to wait in seconds (default: 30)
  poll_rate (float): Time between checks in seconds (default: 1)

Returns:
  bool: True if port is open, False if timeout exceeded`,
		},
		"http": {
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.MinArgs(args, 1); err != nil {
					return err
				}

				url, err := args[0].AsString()
				if err != nil {
					return err
				}

				timeout := 30
				pollRate := 1.0
				expectedStatus := int64(200)

				if len(args) > 1 {
					if t, err := args[1].AsInt(); err == nil {
						timeout = int(t)
					} else {
						return err
					}
				}

				for k, v := range kwargs.Kwargs {
					switch k {
					case "timeout":
						if t, err := v.AsInt(); err == nil {
							timeout = int(t)
						} else {
							return err
						}
					case "poll_rate":
						if f, err := v.AsFloat(); err == nil {
							pollRate = f
						} else if i, err := v.AsInt(); err == nil {
							pollRate = float64(i)
						} else {
							return errors.NewTypeError("FLOAT", v.Type().String())
						}
					case "status_code":
						if s, err := v.AsInt(); err == nil {
							expectedStatus = s
						} else {
							return err
						}
					}
				}

				deadline := time.Now().Add(time.Duration(timeout) * time.Second)
				pollInterval := time.Duration(pollRate * float64(time.Second))

				client := pool.GetHTTPClient()

				for time.Now().Before(deadline) {
					req, httpErr := http.NewRequestWithContext(ctx, "GET", url, nil)
					if httpErr != nil {
						return errors.NewError("http request error: %s", httpErr.Error())
					}

					resp, httpErr := client.Do(req)
					if httpErr == nil {
						statusMatch := int64(resp.StatusCode) == expectedStatus
						resp.Body.Close()
						if statusMatch {
							return &object.Boolean{Value: true}
						}
					}

					select {
					case <-ctx.Done():
						return &object.Boolean{Value: false}
					case <-time.After(pollInterval):

					}
				}

				req, httpErr := http.NewRequestWithContext(ctx, "GET", url, nil)
				if httpErr != nil {
					return &object.Boolean{Value: false}
				}
				if resp, httpErr := client.Do(req); httpErr == nil {
					statusMatch := int64(resp.StatusCode) == expectedStatus
					resp.Body.Close()
					if statusMatch {
						return &object.Boolean{Value: true}
					}
				}
				return &object.Boolean{Value: false}
			},
			HelpText: `http(url, timeout=30, poll_rate=1, status_code=200) - Wait for HTTP endpoint

Waits for the specified HTTP endpoint to respond with the expected status code.

Parameters:
  url (string): URL to check
  timeout (int): Maximum time to wait in seconds (default: 30)
  poll_rate (float): Time between checks in seconds (default: 1)
  status_code (int): Expected HTTP status code (default: 200)

Returns:
  bool: True if endpoint responds with expected status, False if timeout exceeded`,
		},
		"file_content": {
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.MinArgs(args, 2); err != nil {
					return err
				}

				path, err := args[0].AsString()
				if err != nil {
					return err
				}

				content, err := args[1].AsString()
				if err != nil {
					return err
				}

				timeout, pollRate, err := parseWaitOptionsKwargsOnly(30, 1.0, kwargs.Kwargs)
				if err != nil {
					return err
				}

				deadline := time.Now().Add(time.Duration(timeout) * time.Second)
				pollInterval := time.Duration(pollRate * float64(time.Second))

				for time.Now().Before(deadline) {
					if data, err := os.ReadFile(path); err == nil {
						if strings.Contains(string(data), content) {
							return &object.Boolean{Value: true}
						}
					}

					select {
					case <-ctx.Done():
						return &object.Boolean{Value: false}
					case <-time.After(pollInterval):

					}
				}

				if data, err := os.ReadFile(path); err == nil {
					if strings.Contains(string(data), content) {
						return &object.Boolean{Value: true}
					}
				}
				return &object.Boolean{Value: false}
			},
			HelpText: `file_content(path, content, timeout=30, poll_rate=1) - Wait for file to contain content

Waits for the specified file to exist and contain the given content.

Parameters:
  path (string): Path to the file to check
  content (string): Content to search for in the file
  timeout (int): Maximum time to wait in seconds (default: 30)
  poll_rate (float): Time between checks in seconds (default: 1)

Returns:
  bool: True if file contains the content, False if timeout exceeded`,
		},
		"process_name": {
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.MinArgs(args, 1); err != nil {
					return err
				}

				processName, err := args[0].AsString()
				if err != nil {
					return err
				}

				timeout, pollRate, err := parseWaitOptions(args, kwargs.Kwargs)
				if err != nil {
					return err
				}

				deadline := time.Now().Add(time.Duration(timeout) * time.Second)
				pollInterval := time.Duration(pollRate * float64(time.Second))

				for time.Now().Before(deadline) {
					if processRunning(processName) {
						return &object.Boolean{Value: true}
					}

					select {
					case <-ctx.Done():
						return &object.Boolean{Value: false}
					case <-time.After(pollInterval):

					}
				}

				if processRunning(processName) {
					return &object.Boolean{Value: true}
				}
				return &object.Boolean{Value: false}
			},
			HelpText: `process_name(name, timeout=30, poll_rate=1) - Wait for a process to be running

Waits for a process with the specified name to be running.

Parameters:
  name (string): Process name to search for
  timeout (int): Maximum time to wait in seconds (default: 30)
  poll_rate (float): Time between checks in seconds (default: 1)

Returns:
  bool: True if process is running, False if timeout exceeded`,
		},
	},
	nil,
	"Wait for resources to become available",
)
View Source
var YAMLLibrary = object.NewLibrary(YAMLLibraryName, map[string]*object.Builtin{
	"load": {
		Fn: yamlLoadFunc,
		HelpText: `load(yaml_string) - Parse YAML string (deprecated, use safe_load)

Parses a YAML string and returns the corresponding Scriptling object.
Alias for safe_load(). Both functions are identical and safe in Scriptling.

Note: In PyYAML, load() is deprecated. Use safe_load() instead.

Example:
    import yaml
    data = yaml.safe_load("name: John\nage: 30")
    print(data["name"])`,
	},
	"safe_load": {
		Fn: yamlLoadFunc,
		HelpText: `safe_load(yaml_string) - Safely parse YAML string

Safely parses a YAML string and returns the corresponding Scriptling object.

Example:
    import yaml
    data = yaml.safe_load("name: John\nage: 30")
    print(data["name"])`,
	},
	"dump": {
		Fn: yamlDumpFunc,
		HelpText: `dump(obj) - Convert Scriptling object to YAML string (use safe_dump)

Converts a Scriptling object to a YAML string.
Alias for safe_dump(). Both functions are identical in Scriptling.

Example:
    import yaml
    data = {"name": "John", "age": 30}
    yaml_str = yaml.safe_dump(data)
    print(yaml_str)`,
	},
	"safe_dump": {
		Fn: yamlDumpFunc,
		HelpText: `safe_dump(obj) - Safely convert Scriptling object to YAML string

Safely converts a Scriptling object to a YAML string.

Example:
    import yaml
    data = {"name": "John", "age": 30}
    yaml_str = yaml.safe_dump(data)
    print(yaml_str)`,
	},
}, nil, "YAML parsing and generation")

YAMLLibrary provides YAML parsing and generation functionality

Functions

func CreateRequestInstance

func CreateRequestInstance(method, path, body string, headers map[string]string, query map[string]string) *object.Instance

CreateRequestInstance creates a new Request instance with the given data

func NewGlobLibrary

func NewGlobLibrary(config fssecurity.Config) *object.Library

NewGlobLibrary creates a new Glob library with the given configuration.

func NewInputBuiltin added in v0.2.6

func NewInputBuiltin(stdin io.Reader) *object.Builtin

NewInputBuiltin returns an input() builtin backed by the given reader. Callers that manage their own Scriptling instance can use this to inject input() directly via SetObjectVar when the reader is known at a different point than RegisterSysLibrary.

func NewOSLibrary

func NewOSLibrary(config fssecurity.Config) (*object.Library, *object.Library)

NewOSLibrary creates a new OS library with the given configuration. The returned libraries are for "os" and "os.path". Prefer using RegisterOSLibrary which handles registration automatically.

func NewPathlibLibrary

func NewPathlibLibrary(config fssecurity.Config) *object.Library

NewPathlibLibrary creates a new Pathlib library with the given configuration.

func NewRuntimeLibraryWithSubs added in v0.1.1

func NewRuntimeLibraryWithSubs(allowedPaths []string) *object.Library

NewRuntimeLibraryWithSubs creates the runtime library with all sub-libraries including sandbox.

func NewSandboxLibrary added in v0.1.1

func NewSandboxLibrary(allowedPaths []string) *object.Library

NewSandboxLibrary creates a new sandbox library with the given allowed paths. If allowedPaths is nil, all paths are allowed (no restrictions). If allowedPaths is empty slice, no paths are allowed (deny all).

func NewSysLibrary

func NewSysLibrary(argv []string, stdin io.Reader) *object.Library

NewSysLibrary creates a new sys library with the given argv and optional stdin reader.

func RegisterGlobLibrary

func RegisterGlobLibrary(registrar object.LibraryRegistrar, allowedPaths []string)

RegisterGlobLibrary registers the glob library with a Scriptling instance. If allowedPaths is empty or nil, all paths are allowed (no restrictions). If allowedPaths contains paths, all glob operations are restricted to those directories.

SECURITY: When running untrusted scripts, ALWAYS provide allowedPaths to restrict file system access. The security checks prevent: - Reading files outside allowed directories - Path traversal attacks (../../../etc/passwd) - Symlink attacks (symlinks pointing outside allowed dirs)

Example:

No restrictions - full filesystem access (DANGEROUS for untrusted code)
extlibs.RegisterGlobLibrary(s, nil)

Restricted to specific directories (SECURE)
extlibs.RegisterGlobLibrary(s, []string{"/tmp/sandbox", "/home/user/data"})

func RegisterHTMLParserLibrary

func RegisterHTMLParserLibrary(registrar interface{ RegisterLibrary(*object.Library) })

func RegisterLoggingLibrary

func RegisterLoggingLibrary(registrar interface{ RegisterLibrary(*object.Library) }, loggerInstance logger.Logger)

RegisterLoggingLibrary registers the logging library with the given registrar and optional logger Each environment gets its own logger instance

func RegisterLoggingLibraryDefault

func RegisterLoggingLibraryDefault(registrar interface{ RegisterLibrary(*object.Library) })

RegisterLoggingLibraryDefault registers the logging library with default configuration

func RegisterOSLibrary

func RegisterOSLibrary(registrar object.LibraryRegistrar, allowedPaths []string)

RegisterOSLibrary registers the os and os.path libraries with a Scriptling instance. If allowedPaths is empty or nil, all paths are allowed (no restrictions). If allowedPaths contains paths, all file operations are restricted to those directories.

SECURITY: When running untrusted scripts, ALWAYS provide allowedPaths to restrict file system access. The security checks prevent: - Reading/writing files outside allowed directories - Path traversal attacks (../../../etc/passwd) - Symlink attacks (symlinks pointing outside allowed dirs)

Example:

No restrictions - full filesystem access (DANGEROUS for untrusted code)
extlibs.RegisterOSLibrary(s, nil)

Restricted to specific directories (SECURE)
extlibs.RegisterOSLibrary(s, []string{"/tmp/sandbox", "/home/user/data"})

func RegisterPathlibLibrary

func RegisterPathlibLibrary(registrar object.LibraryRegistrar, allowedPaths []string)

RegisterPathlibLibrary registers the pathlib library with a Scriptling instance.

func RegisterRequestsLibrary

func RegisterRequestsLibrary(registrar interface{ RegisterLibrary(*object.Library) })

func RegisterRuntimeHTTPLibrary

func RegisterRuntimeHTTPLibrary(registrar interface{ RegisterLibrary(*object.Library) })

func RegisterRuntimeKVLibrary

func RegisterRuntimeKVLibrary(registrar interface{ RegisterLibrary(*object.Library) })

func RegisterRuntimeLibrary

func RegisterRuntimeLibrary(registrar interface{ RegisterLibrary(*object.Library) })

RegisterRuntimeLibrary registers only the core runtime library (background function). Sub-libraries (http, kv, sync) must be registered separately if needed.

func RegisterRuntimeLibraryAll

func RegisterRuntimeLibraryAll(registrar interface{ RegisterLibrary(*object.Library) }, allowedPaths []string)

RegisterRuntimeLibraryAll registers the runtime library with all sub-libraries, including sandbox with the specified allowed paths for exec_file restrictions. If allowedPaths is nil, all paths are allowed (no restrictions). If allowedPaths is empty slice, no paths are allowed (deny all).

func RegisterRuntimeSandboxLibrary

func RegisterRuntimeSandboxLibrary(registrar interface{ RegisterLibrary(*object.Library) }, allowedPaths []string)

RegisterRuntimeSandboxLibrary registers the sandbox library with the specified allowed paths. If allowedPaths is nil, all paths are allowed (no restrictions). If allowedPaths is empty slice, no paths are allowed (deny all).

func RegisterRuntimeSyncLibrary

func RegisterRuntimeSyncLibrary(registrar interface{ RegisterLibrary(*object.Library) })

func RegisterSecretsLibrary

func RegisterSecretsLibrary(registrar interface{ RegisterLibrary(*object.Library) })

func RegisterSubprocessLibrary

func RegisterSubprocessLibrary(registrar interface{ RegisterLibrary(*object.Library) })

func RegisterSysLibrary

func RegisterSysLibrary(registrar sysRegistrar, argv []string, stdin io.Reader)

func RegisterTOMLLibrary

func RegisterTOMLLibrary(registrar interface{ RegisterLibrary(*object.Library) })

func RegisterWaitForLibrary

func RegisterWaitForLibrary(registrar interface{ RegisterLibrary(*object.Library) })

func RegisterYAMLLibrary

func RegisterYAMLLibrary(registrar interface{ RegisterLibrary(*object.Library) })

func ReleaseBackgroundTasks

func ReleaseBackgroundTasks()

ReleaseBackgroundTasks sets BackgroundReady=true and starts all queued tasks

func ResetRuntime

func ResetRuntime()

ResetRuntime clears all runtime state (for testing or re-initialization)

func SetBackgroundFactory

func SetBackgroundFactory(factory SandboxFactory)

SetBackgroundFactory sets the factory function for creating Scriptling instances in background tasks. Deprecated: Use SetSandboxFactory instead, which sets the factory for both sandbox and background use.

func SetSandboxFactory

func SetSandboxFactory(factory SandboxFactory)

SetSandboxFactory sets the factory function for creating sandbox instances. Must be called before sandbox.create() is used in scripts.

Example:

extlibs.SetSandboxFactory(func() extlibs.SandboxInstance {
    p := scriptling.New()
    setupMyLibraries(p)
    return p
})

func StopKVCleanup

func StopKVCleanup()

StopKVCleanup stops the background KV cleanup goroutine.

Types

type CompletedProcess

type CompletedProcess struct {
	Args       []string
	Returncode int
	Stdout     string
	Stderr     string
}

CompletedProcess represents the result of a subprocess.run call

func (*CompletedProcess) AsBool

func (cp *CompletedProcess) AsBool() (bool, object.Object)

func (*CompletedProcess) AsDict

func (cp *CompletedProcess) AsDict() (map[string]object.Object, object.Object)

func (*CompletedProcess) AsFloat

func (cp *CompletedProcess) AsFloat() (float64, object.Object)

func (*CompletedProcess) AsInt

func (cp *CompletedProcess) AsInt() (int64, object.Object)

func (*CompletedProcess) AsList

func (cp *CompletedProcess) AsList() ([]object.Object, object.Object)

func (*CompletedProcess) AsString

func (cp *CompletedProcess) AsString() (string, object.Object)

func (*CompletedProcess) Inspect

func (cp *CompletedProcess) Inspect() string

func (*CompletedProcess) Type

func (cp *CompletedProcess) Type() object.ObjectType

type GlobLibraryInstance

type GlobLibraryInstance struct {
	// contains filtered or unexported fields
}

GlobLibraryInstance holds the configured Glob library instance

type PathlibLibraryInstance

type PathlibLibraryInstance struct {
	PathClass *object.Class
	// contains filtered or unexported fields
}

PathlibLibraryInstance holds the configured Pathlib library instance

type Promise

type Promise struct {
	// contains filtered or unexported fields
}

Promise represents an async operation result

type RouteInfo

type RouteInfo struct {
	Methods   []string
	Handler   string
	Static    bool
	StaticDir string
}

RouteInfo stores information about a registered route

type RuntimeAtomic

type RuntimeAtomic struct {
	// contains filtered or unexported fields
}

RuntimeAtomic is a named atomic counter

type RuntimeQueue

type RuntimeQueue struct {
	// contains filtered or unexported fields
}

RuntimeQueue is a named thread-safe queue

type RuntimeShared

type RuntimeShared struct {
	// contains filtered or unexported fields
}

RuntimeShared is a named shared value. Values stored should be treated as immutable. Use set() to replace. For atomic read-modify-write, use update() with a callback.

type RuntimeWaitGroup

type RuntimeWaitGroup struct {
	// contains filtered or unexported fields
}

RuntimeWaitGroup is a named wait group

type SandboxFactory

type SandboxFactory func() SandboxInstance

SandboxFactory creates new Scriptling instances for sandbox execution. Must be set by the host application before sandbox.create() can be used. The factory should return a fully configured instance with all required libraries registered and import paths configured.

func GetSandboxFactory added in v0.1.1

func GetSandboxFactory() SandboxFactory

GetSandboxFactory returns the currently configured sandbox factory. Returns nil if no factory has been set.

type SandboxInstance

type SandboxInstance interface {
	SetObjectVar(name string, obj object.Object) error
	GetVarAsObject(name string) (object.Object, error)
	EvalWithContext(ctx context.Context, input string) (object.Object, error)
	SetSourceFile(name string)
	LoadLibraryIntoEnv(name string, env *object.Environment) error
	SetOutputWriter(w io.Writer)
}

SandboxInstance is the minimal interface a sandbox environment needs. This matches the Scriptling public API without importing the scriptling package. It is also used by the background task factory in scriptling.runtime.

Directories

Path Synopsis
ai

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL