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 NewGlobLibrary(config fssecurity.Config) *object.Library
- func NewInputBuiltin(stdin io.Reader) *object.Builtin
- func NewOSLibrary(config fssecurity.Config) (*object.Library, *object.Library)
- func NewPathlibLibrary(config fssecurity.Config) *object.Library
- func NewRuntimeLibraryWithSubs(allowedPaths []string) *object.Library
- func NewSandboxLibrary(allowedPaths []string) *object.Library
- func NewSysLibrary(argv []string, stdin io.Reader) *object.Library
- 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{ ... }, allowedPaths []string)
- func RegisterRuntimeSandboxLibrary(registrar interface{ ... }, allowedPaths []string)
- func RegisterRuntimeSyncLibrary(registrar interface{ ... })
- func RegisterSecretsLibrary(registrar interface{ ... })
- func RegisterSubprocessLibrary(registrar interface{ ... })
- func RegisterSysLibrary(registrar sysRegistrar, argv []string, stdin io.Reader)
- func RegisterTOMLLibrary(registrar interface{ ... })
- func RegisterWaitForLibrary(registrar interface{ ... })
- func RegisterYAMLLibrary(registrar interface{ ... })
- func ReleaseBackgroundTasks()
- func ResetRuntime()
- func SetBackgroundFactory(factory SandboxFactory)
- 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" 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 { 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
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 = 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).
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 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 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
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 ¶
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
NewRuntimeLibraryWithSubs creates the runtime library with all sub-libraries including sandbox.
func NewSandboxLibrary ¶ added in v0.1.1
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 ¶
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 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 ¶
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 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 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.
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.