Documentation
¶
Overview ¶
Package extlibs provides external libraries that need explicit registration
Package extlibs provides external libraries that need explicit registration
Index ¶
- Constants
- Variables
- func NewConsoleLibrary(reader io.Reader) *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 RegisterSecretsLibrary(registrar interface{ ... })
- func RegisterSubprocessLibrary(registrar interface{ ... })
- func RegisterSysLibrary(registrar interface{ ... }, argv []string)
- func RegisterThreadsLibrary(registrar interface{ ... })
- func RegisterWaitForLibrary(registrar interface{ ... })
- type AtomicInt64
- 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 Pool
- type Promise
- type Queue
- type SharedValue
- type SysExitCode
- type WaitGroup
Constants ¶
const ( RequestsLibraryName = "requests" SysLibraryName = "sys" SecretsLibraryName = "secrets" SubprocessLibraryName = "subprocess" HTMLParserLibraryName = "html.parser" OSLibraryName = "os" OSPathLibraryName = "os.path" PathlibLibraryName = "pathlib" GlobLibraryName = "glob" ThreadsLibraryName = "sl.threads" LoggingLibraryName = "logging" WaitForLibraryName = "wait_for" AILibraryName = "sl.ai" MCPLibraryName = "sl.mcp" ToonLibraryName = "sl.toon" ConsoleLibraryName = "sl.console" )
Library names as constants for easy reference
Variables ¶
var ApplyFunctionFunc func(ctx context.Context, fn object.Object, args []object.Object, kwargs map[string]object.Object, env *object.Environment) object.Object
ApplyFunctionFunc is set by the evaluator to allow calling user functions This avoids import cycles
var ApplyMethodFunc func(ctx context.Context, instance *object.Instance, method *object.Function, args []object.Object) object.Object
ApplyMethodFunc is a function type that the evaluator can set to allow calling user methods
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(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 RequestsLibrary = object.NewLibrary(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, **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 **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`, }, "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, **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 **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`, }, "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, **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 **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`, }, }, 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 { var result interface{} if err := json.Unmarshal([]byte(body), &result); err != nil { return errors.NewError("JSONDecodeError: %s", err.Error()) } return convertJSONToObject(result) } } 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 SecretsLibrary = object.NewLibrary(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(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 keyStr, ok := pair.Key.(*object.String); ok { if valStr, ok := pair.Value.(*object.String); ok { env[keyStr.Value] = 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 SysExitCallback func(code int)
SysExitCallback can be set to customize sys.exit() behavior
var ThreadsLibrary = object.NewLibrary(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 } fn := args[0] fnArgs := args[1:] env := getEnvFromContext(ctx) if env == nil { return errors.NewError("async.run: no environment in context") } clonedEnv := cloneEnvironment(env) promise := newPromise() go func() { var result object.Object if ApplyFunctionFunc != nil { result = ApplyFunctionFunc(ctx, fn, fnArgs, kwargs.Kwargs, clonedEnv) } else { result = errors.NewError("async library not properly initialized") } if err, ok := result.(*object.Error); ok { promise.set(nil, fmt.Errorf("%s", err.Message)) } else { promise.set(result, nil) } }() return &object.Builtin{ Attributes: map[string]object.Object{ "get": &object.Builtin{ Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { result, err := promise.get() if err != nil { return errors.NewError("async error: %v", err) } return result }, HelpText: "get() - Wait for and return the result", }, "wait": &object.Builtin{ Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { _, err := promise.get() if err != nil { return errors.NewError("async error: %v", err) } return &object.Null{} }, HelpText: "wait() - Wait for completion and discard the result", }, }, HelpText: "Promise object - call .get() to retrieve result or .wait() to wait without result", } }, HelpText: `run(func, *args, **kwargs) - Run function asynchronously Executes function in a separate goroutine with isolated environment. Returns a Promise object. Call .get() to retrieve the result or .wait() to wait without result. Supports both positional and keyword arguments. Example: def worker(x, y=10): return x + y promise = async.run(worker, 5, y=3) result = promise.get() # Returns 8 # Or just wait for completion: promise.wait() # Waits but discards result`, }, "Atomic": { Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { initial := int64(0) if len(args) > 0 { if i, err := args[0].AsInt(); err == nil { initial = i } else { return errors.NewTypeError("INTEGER", args[0].Type().String()) } } atomic := newAtomicInt64(initial) 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(initial=0) - Create an atomic integer counter Lock-free atomic operations for high-performance counters. Example: counter = async.Atomic(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 { var initial object.Object = &object.Null{} if len(args) > 0 { initial = args[0] } shared := newSharedValue(initial) 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)", }, "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)", }, }, HelpText: "Shared variable - thread-safe access with get()/set()", } }, HelpText: `Shared(initial_value) - Create a thread-safe shared variable For complex types that need mutex protection. Example: shared_list = async.Shared([]) def append_item(item): current = shared_list.get() current.append(item) shared_list.set(current) promises = [async.run(append_item, i) for i in range(100)] for p in promises: p.get()`, }, "WaitGroup": { Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { wg := newWaitGroup() 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() - Create a wait group for synchronizing goroutines Example: wg = async.WaitGroup() def worker(id): print(f"Worker {id}") wg.done() for i in range(10): wg.add(1) async.run(worker, i) wg.wait()`, }, "Queue": { Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { maxsize := 0 if len(args) > 0 { if m, err := args[0].AsInt(); err == nil { maxsize = int(m) } } if m, ok := kwargs.Kwargs["maxsize"]; ok { if mInt, err := m.AsInt(); err == nil { maxsize = int(mInt) } } queue := newQueue(maxsize) 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(args[0]); err != nil { return errors.NewError("queue error: %v", err) } return &object.Null{} }, HelpText: "put(item) - Add item to queue (blocks if full)", }, "get": &object.Builtin{ Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { item, err := queue.get() if err != nil { return errors.NewError("queue error: %v", err) } return item }, HelpText: "get() - Remove and return item from queue (blocks if empty)", }, "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(maxsize=0) - Create a thread-safe queue maxsize=0 creates unbounded queue, maxsize>0 creates bounded queue. Example: queue = async.Queue(maxsize=100) def producer(): for i in range(10): queue.put(i) def consumer(): for i in range(10): item = queue.get() print(item) async.run(producer) async.run(consumer)`, }, "Pool": { Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { if err := errors.MinArgs(args, 1); err != nil { return err } worker := args[0] workers := 4 queueDepth := 0 if len(args) > 1 { if w, err := args[1].AsInt(); err == nil { workers = int(w) } } if w, ok := kwargs.Kwargs["workers"]; ok { if wInt, err := w.AsInt(); err == nil { workers = int(wInt) } } if q, ok := kwargs.Kwargs["queue_depth"]; ok { if qInt, err := q.AsInt(); err == nil { queueDepth = int(qInt) } } env := getEnvFromContext(ctx) if env == nil { return errors.NewError("async.Pool: no environment in context") } pool := newPool(ctx, worker, env, workers, queueDepth) return &object.Builtin{ Attributes: map[string]object.Object{ "submit": &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 := pool.submit(args[0]); err != nil { return errors.NewError("pool submit error: %v", err) } return &object.Null{} }, HelpText: "submit(data) - Submit data to pool for processing", }, "close": &object.Builtin{ Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object { pool.close() return &object.Null{} }, HelpText: "close() - Stop accepting work and wait for completion", }, }, HelpText: "Pool - Worker pool for processing data", } }, HelpText: `Pool(worker_func, workers=4, queue_depth=workers*2) - Create a worker pool worker_func is called with each submitted data item. Example: def process_data(item): print(f"Processing {item}") pool = async.Pool(process_data, workers=4, queue_depth=1000) for i in range(100): pool.submit(i) pool.close()`, }, }, nil, "Asynchronous execution with isolated environments")
ThreadsLibrary provides async execution primitives
var WaitForLibrary = object.NewLibrary( 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", )
Functions ¶
func NewConsoleLibrary ¶
NewConsoleLibrary creates a new console library instance with its own scanner. Each scriptling environment should have its own console library to maintain proper buffering state for the input reader.
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(string, *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 RegisterSecretsLibrary ¶
func RegisterSysLibrary ¶
func RegisterThreadsLibrary ¶
RegisterThreadsLibrary registers the threads library with the given registrar
func RegisterWaitForLibrary ¶
Types ¶
type AtomicInt64 ¶
type AtomicInt64 struct {
// contains filtered or unexported fields
}
AtomicInt64 wraps an atomic int64
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 Pool ¶
type Pool struct {
// contains filtered or unexported fields
}
Pool is a worker pool with a specific worker function
type Promise ¶
type Promise struct {
// contains filtered or unexported fields
}
Promise represents an async operation result
type Queue ¶
type Queue struct {
// contains filtered or unexported fields
}
Queue is a thread-safe queue
type SharedValue ¶
type SharedValue struct {
// contains filtered or unexported fields
}
SharedValue wraps a value with a mutex for thread-safe access
type SysExitCode ¶
type SysExitCode struct {
Code int
}
SysExitCode is used to communicate exit codes from sys.exit()
func (*SysExitCode) Error ¶
func (s *SysExitCode) Error() string