Documentation
¶
Overview ¶
Package extlibs provides external libraries that need explicit registration
Package extlibs provides external libraries that need explicit registration
Index ¶
- Constants
- Variables
- func CreateRequestInstance(method, path, body string, headers map[string]string, query map[string]string) *object.Instance
- func NewConsoleLibrary() *object.Library
- func NewGlobLibrary(config fssecurity.Config) *object.Library
- func NewOSLibrary(config fssecurity.Config) (*object.Library, *object.Library)
- func NewPathlibLibrary(config fssecurity.Config) *object.Library
- func NewSysLibrary(argv []string) *object.Library
- func RegisterConsoleLibrary(registrar interface{ ... })
- func RegisterGlobLibrary(registrar object.LibraryRegistrar, allowedPaths []string)
- func RegisterHTMLParserLibrary(registrar interface{ ... })
- func RegisterLoggingLibrary(registrar interface{ ... }, loggerInstance logger.Logger)
- func RegisterLoggingLibraryDefault(registrar interface{ ... })
- func RegisterOSLibrary(registrar object.LibraryRegistrar, allowedPaths []string)
- func RegisterPathlibLibrary(registrar object.LibraryRegistrar, allowedPaths []string)
- func RegisterRequestsLibrary(registrar interface{ ... })
- func RegisterRuntimeHTTPLibrary(registrar interface{ ... })
- func RegisterRuntimeKVLibrary(registrar interface{ ... })
- func RegisterRuntimeLibrary(registrar interface{ ... })
- func RegisterRuntimeLibraryAll(registrar interface{ ... })
- func RegisterRuntimeSandboxLibrary(registrar interface{ ... })
- func RegisterRuntimeSyncLibrary(registrar interface{ ... })
- func RegisterSecretsLibrary(registrar interface{ ... })
- func RegisterSubprocessLibrary(registrar interface{ ... })
- func RegisterSysLibrary(registrar interface{ ... }, argv []string)
- func RegisterTOMLLibrary(registrar interface{ ... })
- func RegisterWaitForLibrary(registrar interface{ ... })
- func RegisterYAMLLibrary(registrar interface{ ... })
- func ReleaseBackgroundTasks()
- func ResetRuntime()
- func SetBackgroundFactory(factory SandboxFactory)
- func SetSandboxAllowedPaths(allowedPaths []string)
- func SetSandboxFactory(factory SandboxFactory)
- func StopKVCleanup()
- type CompletedProcess
- func (cp *CompletedProcess) AsBool() (bool, object.Object)
- func (cp *CompletedProcess) AsDict() (map[string]object.Object, object.Object)
- func (cp *CompletedProcess) AsFloat() (float64, object.Object)
- func (cp *CompletedProcess) AsInt() (int64, object.Object)
- func (cp *CompletedProcess) AsList() ([]object.Object, object.Object)
- func (cp *CompletedProcess) AsString() (string, object.Object)
- func (cp *CompletedProcess) Inspect() string
- func (cp *CompletedProcess) Type() object.ObjectType
- type GlobLibraryInstance
- type PathlibLibraryInstance
- type Promise
- type RouteInfo
- type RuntimeAtomic
- type RuntimeQueue
- type RuntimeShared
- type RuntimeWaitGroup
- type SandboxFactory
- type SandboxInstance
Constants ¶
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" ConsoleLibraryName = "scriptling.console" YAMLLibraryName = "yaml" AgentLibraryName = "scriptling.ai.agent" InteractLibraryName = "scriptling.ai.agent.interact" FuzzyLibraryName = "scriptling.fuzzy" TOMLLibraryName = "toml" )
Library names as constants for easy reference
const RuntimeLibraryName = "scriptling.runtime"
Variables ¶
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
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
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")
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.")
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
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")
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 { if statusCode >= 500 { return errors.NewError("HTTPError: %d Server Error", statusCode) } else { return errors.NewError("HTTPError: %d Client Error", statusCode) } } 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
var RuntimeLibrary = RuntimeLibraryWithSubs
RuntimeLibrary is an alias for RuntimeLibraryWithSubs for backward compatibility
var RuntimeLibraryCore = object.NewLibrary(RuntimeLibraryName, RuntimeLibraryFunctions, nil, "Runtime library for background tasks")
RuntimeLibraryCore is the runtime library without sub-libraries
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)
var RuntimeLibraryWithSubs = object.NewLibraryWithSubs(RuntimeLibraryName, RuntimeLibraryFunctions, nil, map[string]*object.Library{ "http": HTTPSubLibrary, "kv": KVSubLibrary, "sync": SyncSubLibrary, "sandbox": SandboxSubLibrary, }, "Runtime library for HTTP, KV store, concurrency primitives, and sandboxed execution")
RuntimeLibraryWithSubs is the runtime library with all sub-libraries (http, kv, sync, sandbox)
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
var SandboxSubLibrary = buildSandboxLibrary()
SandboxSubLibrary is the sandbox sub-library for scriptling.runtime. Access it as scriptling.runtime.sandbox in scripts.
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
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")
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")
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
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", )
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 NewConsoleLibrary ¶
NewConsoleLibrary creates a new console library instance. The library reads from the environment's input reader (defaults to os.Stdin) and writes prompts to the environment's output writer (defaults to os.Stdout). Note: Each call to input() creates a new scanner, so the reader must maintain its position between calls (e.g., strings.Reader, os.Stdin, etc.)
func NewGlobLibrary ¶
func NewGlobLibrary(config fssecurity.Config) *object.Library
NewGlobLibrary creates a new Glob library with the given configuration.
func NewOSLibrary ¶
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 NewSysLibrary ¶
NewSysLibrary creates a new sys library with the given argv
func RegisterConsoleLibrary ¶
RegisterConsoleLibrary registers the console library with a scriptling instance
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 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 ¶
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 RegisterRuntimeLibrary ¶
RegisterRuntimeLibrary registers only the core runtime library (background function). Sub-libraries (http, kv, sync) must be registered separately if needed.
func RegisterRuntimeLibraryAll ¶
RegisterRuntimeLibraryAll registers the runtime library with all sub-libraries (http, kv, sync, sandbox).
func RegisterSecretsLibrary ¶
func RegisterSysLibrary ¶
func RegisterTOMLLibrary ¶
func RegisterWaitForLibrary ¶
func RegisterYAMLLibrary ¶
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 SetSandboxAllowedPaths ¶
func SetSandboxAllowedPaths(allowedPaths []string)
SetSandboxAllowedPaths restricts exec_file() to the given directories. If allowedPaths is empty or nil, all paths are allowed (no restrictions). Paths are normalized to absolute paths at set time.
SECURITY: When running untrusted scripts, ALWAYS provide allowedPaths to restrict which script files the sandbox can load and execute.
Example:
extlibs.SetSandboxAllowedPaths([]string{"/opt/scripts"})
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
})
Types ¶
type CompletedProcess ¶
CompletedProcess represents the result of a subprocess.run call
func (*CompletedProcess) AsList ¶
func (cp *CompletedProcess) AsList() ([]object.Object, 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 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.
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.