extlibs

package
v0.21.0 Latest Latest
Warning

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

Go to latest
Published: Aug 21, 2026 License: MIT Imports: 60 Imported by: 0

Documentation

Overview

Package extlibs provides external libraries that need explicit registration

Package extlibs provides external libraries that need explicit registration

Package extlibs provides external libraries that need explicit registration

Package extlibs provides external libraries that need explicit registration

Package extlibs provides external libraries that need explicit registration

Package extlibs provides external libraries that need explicit registration

Package extlibs provides external libraries that need explicit registration

Package extlibs provides external libraries that need explicit registration

Package extlibs provides external libraries that need explicit registration

Package extlibs provides external libraries that need explicit registration

Package extlibs provides external libraries that need explicit registration

Package extlibs provides external libraries that need explicit registration

Package extlibs provides external libraries that need explicit registration

Package extlibs provides external libraries that need explicit registration

Index

Constants

View Source
const (
	RequestsLibraryName       = "requests"
	SysLibraryName            = "sys"
	SecretsLibraryName        = "secrets"
	SubprocessLibraryName     = "subprocess"
	HTMLParserLibraryName     = "html.parser"
	OSLibraryName             = "os"
	OSPathLibraryName         = "os.path"
	PathlibLibraryName        = "pathlib"
	GlobLibraryName           = "glob"
	TempfileLibraryName       = "tempfile"
	ShutilLibraryName         = "shutil"
	ShlexLibraryName          = "shlex"
	ZipfileLibraryName        = "zipfile"
	TarfileLibraryName        = "tarfile"
	CsvLibraryName            = "scriptling.csv"
	XmlLibraryName            = "scriptling.xml"
	LoggingLibraryName        = "logging"
	WaitForLibraryName        = "scriptling.wait_for"
	RuntimeHTTPLibraryName    = "scriptling.runtime.http"
	RuntimeKVLibraryName      = "scriptling.runtime.kv"
	RuntimeSyncLibraryName    = "scriptling.runtime.sync"
	RuntimeSandboxLibraryName = "scriptling.runtime.sandbox"
	RuntimeJSONRPCLibraryName = "scriptling.runtime.jsonrpc"
	RuntimePluginLibraryName  = "scriptling.runtime.plugin"
	AILibraryName             = "scriptling.ai"
	MCPLibraryName            = "scriptling.mcp"
	ToonLibraryName           = "scriptling.toon"
	YAMLLibraryName           = "yaml"
	AgentLibraryName          = "scriptling.ai.agent"
	InteractLibraryName       = "scriptling.ai.agent.interact"
	SimilarityLibraryName     = "scriptling.similarity"
	SecretLibraryName         = "scriptling.secret"
	TOMLLibraryName           = "toml"
	WebSocketLibraryName      = "scriptling.net.websocket"
	MulticastLibraryName      = "scriptling.net.multicast"
	UnicastLibraryName        = "scriptling.net.unicast"
	GossipLibraryName         = "scriptling.net.gossip"
	ResolveLibraryName        = "scriptling.net.resolve"
	FSLibraryName             = "fs"
	GrepLibraryName           = "scriptling.grep"
	FindLibraryName           = "scriptling.find"
	SedLibraryName            = "scriptling.sed"
	ContainerLibraryName      = "scriptling.container"
	NomadLibraryName          = "scriptling.nomad"
	TemplateHTMLLibraryName   = "scriptling.template.html"
	TemplateTextLibraryName   = "scriptling.template.text"
	FileProvisionLibraryName  = "scriptling.provision.file"
	FetchProvisionLibraryName = "scriptling.provision.fetch"
	MarkdownLibraryName       = "scriptling.markdown"
)

Library names as constants for easy reference

View Source
const (
	RuntimeMCPLibraryName = "scriptling.runtime.mcp"

	// MCPRegistryVar is the environment variable name where the mcp.tool()
	// decorator records tool registrations. The folder scanner reads this after
	// evaluating a .py file to discover decorated tools.
	MCPRegistryVar = "__mcp_registry"
)
View Source
const RuntimeLibraryName = "scriptling.runtime"

Variables

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

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

CompletedProcessClass defines the CompletedProcess class

View Source
var ErrPathNotAllowed = errors.New("path is outside allowed directories")

ErrPathNotAllowed is returned when a requested path falls outside the AllowedPaths configured on the options struct.

View Source
var ErrSearchNotFound = errors.New("search text not found")

ErrSearchNotFound is returned by EditFile when the search text does not appear in the file at all.

View Source
var ErrSearchNotUnique = errors.New("search text matched multiple times")

ErrSearchNotUnique is returned by EditFile when the search text appears more than once. The caller should provide more surrounding context to disambiguate.

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

HTMLParserLibrary provides Python-compatible html.parser functionality

View Source
var HTTPSubLibrary = object.NewLibrary(RuntimeHTTPLibraryName, map[string]*object.Builtin{
	"get":    httpVerbEntry("GET"),
	"post":   httpVerbEntry("POST"),
	"put":    httpVerbEntry("PUT"),
	"delete": httpVerbEntry("DELETE"),

	"route": {
		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
			}

			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"}
			}

			if len(args) == 1 {
				return makeHTTPDecorator(ctx, func(ref string) {
					for _, m := range methods {
						registerHTTPRoute(m, path, ref)
					}
				})
			}

			handler, err := args[1].AsString()
			if err != nil {
				return err
			}
			for _, m := range methods {
				registerHTTPRoute(m, path, handler)
			}
			return &object.Null{}
		},
		HelpText: `route(path, handler=None, methods=["GET","POST","PUT","DELETE"]) - Register a route for multiple methods, or use as decorator

Decorator form:
  @http.route("/api", methods=["GET", "POST"])
  def handler(request):
      ...

Imperative form:
  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
			}

			if fn, ok := args[0].(*object.Function); ok {
				env := evaluator.GetEnvFromContext(ctx)
				ref := resolveModuleRef(env, fn.Name)
				if ref == "" {
					return errors.NewError("cannot determine module name for @http.middleware decorator")
				}
				RuntimeState.Lock()
				RuntimeState.Middleware = ref
				RuntimeState.Unlock()
				return fn
			}

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

			RuntimeState.Lock()
			if RuntimeState.ServerStarted {
				fmt.Fprintf(os.Stderr, "warning: runtime.http.middleware registered after start_server() — will not be applied\n")
			}
			RuntimeState.Middleware = handler
			RuntimeState.Unlock()

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

Decorator form:
  @http.middleware
  def auth(request):
      ...

Imperative form:
  runtime.http.middleware("auth.check_request")

The middleware receives the request object and should return:
  - None to continue to the handler
  - A response dict to short-circuit (block the 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()
			if RuntimeState.ServerStarted {
				fmt.Fprintf(os.Stderr, "warning: runtime.http.static %q registered after start_server() — route will not be served\n", path)
			}
			RuntimeState.Routes["GET "+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.NewString(""))
		},
		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.NewString("")

			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.NewString("")

			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.NewString(key)
				dk := object.DictKey(keyObj)
				if len(vals) == 1 {
					pairs[dk] = object.DictPair{
						Key:   keyObj,
						Value: object.NewString(vals[0]),
					}
				} else {
					elements := make([]object.Object, len(vals))
					for i, v := range vals {
						elements[i] = object.NewString(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")`,
	},

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

			if fn, ok := args[0].(*object.Function); ok {
				env := evaluator.GetEnvFromContext(ctx)
				ref := resolveModuleRef(env, fn.Name)
				if ref == "" {
					return errors.NewError("cannot determine module name for @http.not_found decorator")
				}
				RuntimeState.Lock()
				RuntimeState.NotFoundHandler = ref
				RuntimeState.Unlock()
				return fn
			}

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

			RuntimeState.Lock()
			if RuntimeState.ServerStarted {
				fmt.Fprintf(os.Stderr, "warning: runtime.http.not_found registered after start_server() — will not be applied\n")
			}
			RuntimeState.NotFoundHandler = handler
			RuntimeState.Unlock()

			return &object.Null{}
		},
		HelpText: `not_found(handler) - Register a 404 handler, or use as bare decorator

Decorator form:
  @http.not_found
  def handle_404(request):
      ...

Imperative form:
  runtime.http.not_found("handlers.not_found")

The handler receives the request object and should return a response.
It is called when no route matches the request path, or when a static
asset is not found.`,
	},

	"websocket": {
		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
			}

			if len(args) == 1 {
				return makeHTTPDecorator(ctx, func(ref string) {
					RuntimeState.Lock()
					RuntimeState.WebSocketRoutes[path] = &WebSocketRouteInfo{Handler: ref}
					RuntimeState.Unlock()
				})
			}

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

			RuntimeState.Lock()
			if RuntimeState.ServerStarted {
				fmt.Fprintf(os.Stderr, "warning: runtime.http.websocket %q registered after start_server() — route will not be served\n", path)
			}
			RuntimeState.WebSocketRoutes[path] = &WebSocketRouteInfo{
				Handler: handler,
			}
			RuntimeState.Unlock()

			return &object.Null{}
		},
		HelpText: `websocket(path, handler=None) - Register a WebSocket route, or use as decorator

Decorator form:
  @http.websocket("/ws")
  def chat_handler(client):
      client.send("Welcome!")
      while client.connected():
          msg = client.receive(timeout=60)
          if msg:
              client.send("Echo: " + msg)

Imperative form:
  runtime.http.websocket("/chat", "handlers.chat_handler")

The handler receives a WebSocketClient object and runs for the connection lifetime.`,
	},
}, map[string]object.Object{
	"Request":         RequestClass,
	"WebSocketClient": WebSocketClientClass,
}, "HTTP server route registration and response helpers")
View Source
var JSONRPCErrorClass = &object.Class{
	Name: "JSONRPCError",
}

JSONRPCErrorClass is the class for error objects produced by runtime.jsonrpc.error(). The stdio server recognises instances of this class and emits a JSON-RPC error response instead of a result.

View Source
var JSONRPCSubLibrary = object.NewLibrary(RuntimeJSONRPCLibraryName, map[string]*object.Builtin{
	"method": {
		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
			}

			if len(args) == 1 {
				return makeJSONRPCDecorator(ctx, func(ref string) {
					registerJSONRPCMethod(name, ref)
				})
			}

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

			RuntimeState.Lock()
			if RuntimeState.ServerStarted {
				fmt.Fprintf(os.Stderr, "warning: runtime.jsonrpc.method %q registered after start_server() — method will not be served\n", name)
			}
			RuntimeState.JSONRPCMethods[name] = handler
			RuntimeState.Unlock()

			return &object.Null{}
		},
		HelpText: `method(name, handler=None) - Register a JSON-RPC method handler, or use as decorator

Decorator form:
  import scriptling.jsonrpc as jsonrpc

  @jsonrpc.method("echo")
  def echo(params):
      return params

Imperative form:
  runtime.jsonrpc.method("echo", "handlers.echo")

The handler receives the decoded JSON-RPC params as its single argument and
returns a JSON-compatible result. Return runtime.jsonrpc.error(...) to produce
an error response.`,
	},

	"notification": {
		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
			}

			if len(args) == 1 {
				return makeJSONRPCDecorator(ctx, func(ref string) {
					registerJSONRPCNotification(name, ref)
				})
			}

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

			RuntimeState.Lock()
			if RuntimeState.ServerStarted {
				fmt.Fprintf(os.Stderr, "warning: runtime.jsonrpc.notification %q registered after start_server() — notification will not be served\n", name)
			}
			RuntimeState.JSONRPCNotifications[name] = handler
			RuntimeState.Unlock()

			return &object.Null{}
		},
		HelpText: `notification(name, handler=None) - Register a JSON-RPC notification handler, or use as decorator

Decorator form:
  @jsonrpc.notification("updated")
  def on_updated(params):
      ...

Imperative form:
  runtime.jsonrpc.notification("updated", "handlers.on_updated")

Notifications are JSON-RPC requests without an id. The handler receives the
decoded params and no response is written.`,
	},

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

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

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

			var data object.Object
			if len(args) >= 3 {
				data = args[2]
			}

			return CreateJSONRPCErrorInstance(code, message, data)
		},
		HelpText: `error(code, message, data=None) - Build a JSON-RPC error response

Parameters:
  code (int): JSON-RPC error code (e.g. -32602 for invalid params)
  message (str): Human-readable error message
  data (any, optional): Optional structured data attached to the error

Return this from a method handler to emit a JSON-RPC error response with a
custom code. If omitted the response uses the given code and message.

Example:
  def divide(params):
      if params["b"] == 0:
          return runtime.jsonrpc.error(-32602, "division by zero")
      return params["a"] / params["b"]`,
	},
}, map[string]object.Object{
	"JSONRPCError": JSONRPCErrorClass,
}, "stdio JSON-RPC 2.0 server method and notification registration")

JSONRPCSubLibrary exposes runtime.jsonrpc for registering stdio JSON-RPC 2.0 method and notification handlers. Handlers are referenced by string ("library.function") and run on a fresh evaluator per request, matching the runtime.http / MCP / WebSocket concurrency model.

View Source
var MCPSubLibrary = object.NewLibrary(RuntimeMCPLibraryName, map[string]*object.Builtin{
	"tool": {
		Fn: mcpToolDecorator,
		HelpText: `tool(description, params=None, keywords=None, discoverable=False) - Decorator for MCP tools

Decorates a function to register it as an MCP tool. The function's parameters
become the tool's input schema; the return value becomes the tool response.

Parameters:
  description (str): Tool description shown to the AI
  params (dict, optional): Parameter metadata keyed by name. Each value is either
    a string (the description; type inferred from default or defaults to "string")
    or a dict with keys "type", "description", and optional "required".
  keywords (list, optional): Keywords for tool search/discovery
  discoverable (bool, optional): If True, tool is hidden from tools/list and
    only available via search (default: False)

Returns:
  A decorator function that registers the tool and returns the original function.

Example:
  import scriptling.runtime.mcp as mcp

  @mcp.tool(
      description="Calculate a mathematical expression",
      params={"expr": "Expression to evaluate (e.g. 2+3*4)"},
  )
  def calc(expr):
      return f"{expr} = {eval(expr)}"

  @mcp.tool(description="Greet someone", params={
      "name": "Name of the person",
      "times": {"type": "int", "description": "Number of greetings"},
  })
  def greet(name, times=1):
      return "\n".join(f"Hello, {name}!" for _ in range(times))`,
	},
}, nil, "MCP tool, resource, and prompt registration via decorators")

MCPSubLibrary is the scriptling.runtime.mcp sub-library. It provides decorator functions for defining MCP tools (and in future, resources and prompts) from script code. Registrations are recorded per-interpreter in __mcp_registry rather than the global RuntimeState.

View Source
var MarkdownLibrary = object.NewLibrary(MarkdownLibraryName, map[string]*object.Builtin{
	"to_html": {
		Fn: markdownToHTMLFunc,
		HelpText: `to_html(markdown_string) - Convert Markdown to HTML

Converts a Markdown string to an HTML string using the GitHub Flavored
Markdown (GFM) specification. Supports headings, bold, italic, code blocks,
fenced code, blockquotes, ordered and unordered lists, tables, strikethrough,
task lists, and auto-linked URLs.

Args:
    markdown_string (str): The Markdown source to convert.

Returns:
    str: HTML representation of the Markdown input.

Example:
    import scriptling.markdown as markdown

    html = markdown.to_html("# Hello\n\nThis is **bold** and _italic_ text.")
    print(html)
    # <h1 id="hello">Hello</h1>
    # <p>This is <strong>bold</strong> and <em>italic</em> text.</p>

    html = markdown.to_html("- item one\n- item two")
    print(html)
    # <ul>
    # <li>item one</li>
    # <li>item two</li>
    # </ul>`,
	},
}, nil, "Markdown parsing and conversion")

MarkdownLibrary provides Markdown parsing and conversion to HTML.

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

	"serve": {
		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
			}

			version := ""
			if len(args) >= 2 {
				v, e := args[1].AsString()
				if e != nil {
					return e
				}
				version = v
			}
			description := ""
			if len(args) >= 3 {
				d, e := args[2].AsString()
				if e != nil {
					return e
				}
				description = d
			}

			RuntimeState.Lock()
			if RuntimeState.ServerStarted {
				fmt.Fprintf(os.Stderr, "warning: runtime.plugin.serve() called after start_server() — plugin identity will not be used\n")
			}
			RuntimeState.PluginName = name
			RuntimeState.PluginVersion = version
			RuntimeState.PluginDescription = description
			RuntimeState.Unlock()

			return &object.Null{}
		},
		HelpText: `serve(name, version="", description="") - Declare this script as a plugin server

When runtime.start_server() is called in stdio mode the server serves the full
Scriptling plugin protocol (scriptling.handshake, function.call, etc.) instead
of the plain JSON-RPC loop. Clients can then load the script as a plugin peer
with scriptling=True and get auto-generated proxy libraries.

Parameters:
  name (str):        Library name (e.g. "myservice"). Clients import it as plugin.<name>.
  version (str):     Optional version string (e.g. "1.0.0").
  description (str): Optional human-readable description.

Example:
  import scriptling.runtime.plugin as plugin_srv

  plugin_srv.serve("calculator", "1.0", "Basic arithmetic operations")
  plugin_srv.register_function("add", "handlers.add")
  import scriptling.runtime as runtime
  runtime.start_server()`,
	},

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

			if fn, ok := args[0].(*object.Function); ok {
				env := evaluator.GetEnvFromContext(ctx)
				ref := resolveModuleRef(env, fn.Name)
				if ref == "" {
					return errors.NewError("cannot determine module name for @plugin.register_function decorator")
				}
				RuntimeState.Lock()
				RuntimeState.PluginFunctions[fn.Name] = ref
				RuntimeState.Unlock()
				return fn
			}

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

			if len(args) == 1 {
				env := evaluator.GetEnvFromContext(ctx)
				return &object.Builtin{
					Fn: func(_ context.Context, _ object.Kwargs, dArgs ...object.Object) object.Object {
						if len(dArgs) == 0 {
							return errors.NewError("decorator requires a function")
						}
						fn, ok := dArgs[0].(*object.Function)
						if !ok {
							return errors.NewError("decorated value must be a function, got %s", dArgs[0].Type())
						}
						ref := resolveModuleRef(env, fn.Name)
						if ref == "" {
							return errors.NewError("cannot determine module name for decorator")
						}
						RuntimeState.Lock()
						RuntimeState.PluginFunctions[name] = ref
						RuntimeState.Unlock()
						return fn
					},
				}
			}

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

			RuntimeState.Lock()
			if RuntimeState.ServerStarted {
				fmt.Fprintf(os.Stderr, "warning: runtime.plugin.register_function %q registered after start_server() — function will not be served\n", name)
			}
			RuntimeState.PluginFunctions[name] = handler
			RuntimeState.Unlock()

			return &object.Null{}
		},
		HelpText: `register_function(name, handler=None) - Register a function for the plugin server, or use as decorator

Decorator form:
  import scriptling.runtime.plugin as plugin

  @plugin.register_function("add")
  def add(a, b):
      return a + b

  # Bare form uses the function name as the plugin name
  @plugin.register_function
  def greet(name):
      return "hello " + name

Imperative form:
  plugin.register_function("add", "handlers.add")`,
	},

	"register_constant": {
		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
			}

			RuntimeState.Lock()
			if RuntimeState.ServerStarted {
				fmt.Fprintf(os.Stderr, "warning: runtime.plugin.register_constant %q registered after start_server() — constant will not be served\n", name)
			}
			RuntimeState.PluginConstants[name] = args[1]
			RuntimeState.Unlock()

			return &object.Null{}
		},
		HelpText: `register_constant(name, value) - Register a constant exported by the plugin server

Parameters:
  name (str):  Constant name exposed to plugin clients.
  value (any): Value — any type that the plugin transport can encode (bool, int,
               float, string, list, dict, None).

Constants are included in the handshake schema so clients can read them
directly as attributes of the plugin library.

Example:
  import scriptling.runtime.plugin as plugin_srv

  plugin_srv.register_constant("VERSION", "1.0.0")
  plugin_srv.register_constant("MAX_RETRIES", 5)`,
	},

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

			if cls, ok := args[0].(*object.Class); ok {
				env := evaluator.GetEnvFromContext(ctx)
				ref := resolveModuleRef(env, cls.Name)
				if ref == "" {
					return errors.NewError("cannot determine module name for @plugin.register_class decorator")
				}
				RuntimeState.Lock()
				RuntimeState.PluginClasses[cls.Name] = ref
				RuntimeState.Unlock()
				return cls
			}

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

			name := handler
			for i := len(handler) - 1; i >= 0; i-- {
				if handler[i] == '.' {
					name = handler[i+1:]
					break
				}
			}

			RuntimeState.Lock()
			if RuntimeState.ServerStarted {
				fmt.Fprintf(os.Stderr, "warning: runtime.plugin.register_class %q registered after start_server() — class will not be served\n", name)
			}
			RuntimeState.PluginClasses[name] = handler
			RuntimeState.Unlock()

			return &object.Null{}
		},
		HelpText: `register_class(handler) - Register a class exported by the plugin server, or use as bare decorator

Decorator form:
  import scriptling.runtime.plugin as plugin

  @plugin.register_class
  class Config:
      def __init__(self):
          self.version = "1.0"

Imperative form:
  plugin.register_class("handlers.Config")

The exposed class name is derived from the class name (decorator) or the last
segment of the handler ref (imperative).`,
	},
}, nil, "Scriptling plugin server — declare this script as a plugin peer with full handshake support")

PluginSubLibrary exposes runtime.plugin for declaring a script as a Scriptling plugin server. When start_server() is called the CLI replaces the plain JSON-RPC loop with a full plugin.Server that handles the plugin handshake, function.call, and object lifecycle. Available in the agent variant of scriptling only.

View Source
var RequestClass = &object.Class{
	Name: "Request",
	Methods: map[string]object.Object{
		"json": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 1); err != nil {
					return err
				}
				instance, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewError("json() called on non-Request object")
				}

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

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

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

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

RequestClass is the class for Request objects passed to handlers

View Source
var RequestsLibrary = newRequestsLibrary(nil)
View Source
var ResponseClass = &object.Class{
	Name: "Response",
	Methods: map[string]object.Object{
		"json": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 1); err != nil {
					return err
				}
				if instance, ok := args[0].(*object.Instance); ok {
					if body, err := instance.Field("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.Field("status_code").AsInt(); err == nil {
						if statusCode >= 400 {
							kind := "Client"
							if statusCode >= 500 {
								kind = "Server"
							}
							return &object.Exception{
								ExceptionType: "HTTPError",
								Message:       fmt.Sprintf("HTTPError: %d %s Error", statusCode, kind),
							}
						}
						return &object.Null{}
					}
				}
				return errors.NewError("raise_for_status() called on non-Response object")
			},
			HelpText: `raise_for_status() - Raises an exception if the status code indicates an error`,
		},
	},
}

ResponseClass defines the Response class with its methods

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

RuntimeLibraryCore is the runtime library without sub-libraries

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

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

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

			env := getEnvFromContext(ctx)
			eval := evaliface.FromContext(ctx)

			shared := false
			if v := kwargs.Get("shared"); v != nil {
				if b, e := v.AsBool(); e == nil {
					shared = b
				}
			}
			if shared {

				sharedKwargs := make(map[string]object.Object, len(kwargs.Kwargs))
				for k, v := range kwargs.Kwargs {
					if k == "shared" {
						continue
					}
					sharedKwargs[k] = v
				}

				liveArgs := make([]object.Object, len(args)-2)
				copy(liveArgs, args[2:])
				return startSharedTask(ctx, handler, liveArgs, sharedKwargs, env, eval)
			}

			for i, a := range args[2:] {
				if err := object.ValidateTransferable(a); err != nil {
					return errors.NewError("background arg %d: %s", i, err)
				}
			}
			for k, v := range kwargs.Kwargs {
				if err := object.ValidateTransferable(v); err != nil {
					return errors.NewError("background kwarg '%s': %s", k, err)
				}
			}

			fnArgs := make([]object.Object, len(args[2:]))
			for i, a := range args[2:] {
				fnArgs[i] = object.CloneObject(a)
			}
			fnKwargs := make(map[string]object.Object, len(kwargs.Kwargs))
			for k, v := range kwargs.Kwargs {
				fnKwargs[k] = object.CloneObject(v)
			}

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

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

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

Starts a background task in a goroutine and returns immediately.
Returns null on success, or an error if the handler is not found.

  Both handler patterns run in isolated environments with no
  access to the calling script's data. Only sibling functions
  are copied; data must be passed via args or runtime.sync.

Parameters:
  name (string): Unique name for the background task
  handler (string): Function name or "library.function"
    "func_name" - runs in isolated env with import support and sibling functions
    "lib.func" - loads library.function in a new Scriptling instance
  *args: Positional arguments to pass to the function
  **kwargs: Keyword arguments to pass to the function

Arguments must be transferable types — only simple values and
recursively transferable containers are allowed:
  - Scalars: None, bool, int, float, str
  - Containers: list, dict, set, tuple (elements must also be
    transferable)
  - Not allowed: instances, classes, functions, builtins, or any
    other runtime-backed objects
Arguments are deep-copied before the task starts so the caller and
task cannot race on shared state.

Returns:
  null on success, error if handler validation fails

  Background tasks are fire-and-forget. For coordination between
  tasks use runtime.sync primitives (Shared, Atomic, Queue, WaitGroup).
  Access panels via console.Console().panel("name") from background tasks.

`,
	},

	"start_server": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			wait := true
			if v := kwargs.Get("wait"); v != nil {
				if b, e := v.AsBool(); e == nil {
					wait = b
				}
			}

			RuntimeState.Lock()
			if !RuntimeState.ServerStarted && RuntimeState.ServerStartCh != nil {
				RuntimeState.ServerStarted = true
				if RuntimeState.ServerCollect != nil {
					RuntimeState.ServerCollect()
				}
				close(RuntimeState.ServerStartCh)
			}
			RuntimeState.Unlock()

			if wait {
				object.RunBlocking(ctx, func() {
					RuntimeState.RLock()
					ch := RuntimeState.ServerRunningCh
					RuntimeState.RUnlock()
					if ch != nil {
						<-ch
					}
				})
			}
			return &object.Null{}
		},
		HelpText: `start_server(wait=True) - Signal the server to start accepting requests

Signals the server to collect registered routes/methods and begin
listening for requests. Call this after all routes are registered.

Parameters:
  wait (bool, default True): If True, blocks until the server shuts
    down. If False, returns immediately so the script can continue
    running (e.g. to maintain gossip state or run a polling loop).

When wait=True the call blocks until the server receives a shutdown
signal (SIGTERM / Ctrl-C). Use wait=False combined with a
server_running() loop to stay alive while performing other work:

  runtime.start_server(wait=False)
  while runtime.server_running():
      yield_now()

Backward compatibility: scripts that exit without calling
start_server() continue to work — the server starts automatically
after the setup script finishes.

`,
	},

	"server_running": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			RuntimeState.RLock()
			ch := RuntimeState.ServerRunningCh
			RuntimeState.RUnlock()
			if ch == nil {
				return object.NewBoolean(false)
			}
			select {
			case <-ch:
				return object.NewBoolean(false)
			default:
				return object.NewBoolean(true)
			}
		},
		HelpText: `server_running() - Returns True while the server is running

Returns True as long as the server has not received a shutdown signal.
Returns False once the server is shutting down or if called outside
of server mode.

Typical usage with start_server(wait=False):

  runtime.start_server(wait=False)
  while runtime.server_running():
      yield_now()       # release GIL on each iteration

`,
	},
}

RuntimeLibraryFunctions contains the core runtime functions (background)

View Source
var RuntimeState = struct {
	sync.RWMutex

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

	// JSON-RPC stdio methods and notifications (name -> "library.function")
	JSONRPCMethods       map[string]string
	JSONRPCNotifications map[string]string

	// WebSocket routes and connections
	WebSocketRoutes      map[string]*WebSocketRouteInfo
	WebSocketConnections map[string]*WebSocketServerConn

	// 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
	KVDB *snapshotkv.DB

	// Sync primitives
	WaitGroups map[string]*RuntimeWaitGroup
	Queues     map[string]*RuntimeQueue
	Atomics    map[string]*RuntimeAtomic
	Shareds    map[string]*RuntimeShared

	// Server lifecycle channels (nil in script mode)
	ServerStartCh   chan struct{} // closed by start_server() to signal server is ready
	ServerRunningCh chan struct{} // closed by server on shutdown
	ServerStarted   bool          // prevents double-close of ServerStartCh
	ServerCollect   func()        // set by NewServer; called inside start_server() to snapshot routes atomically

	// Plugin server registration (set via runtime.plugin, agent variant only)
	PluginName        string
	PluginVersion     string
	PluginDescription string
	PluginFunctions   map[string]string        // function name → "library.function" handler
	PluginConstants   map[string]object.Object // constant name → value
	PluginClasses     map[string]string        // exposed class name → "library.ClassName" handler

	// Cleanup functions registered by libraries
	cleanupFuncs []func()
}{
	Routes:               make(map[string]*RouteInfo),
	NotFoundHandler:      "",
	JSONRPCMethods:       make(map[string]string),
	JSONRPCNotifications: make(map[string]string),
	WebSocketRoutes:      make(map[string]*WebSocketRouteInfo),
	WebSocketConnections: make(map[string]*WebSocketServerConn),
	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,
	KVDB:                 nil,
	WaitGroups:           make(map[string]*RuntimeWaitGroup),
	Queues:               make(map[string]*RuntimeQueue),
	Atomics:              make(map[string]*RuntimeAtomic),
	Shareds:              make(map[string]*RuntimeShared),
	ServerStartCh:        nil,
	ServerRunningCh:      nil,
	ServerStarted:        false,
	PluginFunctions:      make(map[string]string),
	PluginConstants:      make(map[string]object.Object),
	PluginClasses:        make(map[string]string),
}

RuntimeState holds all runtime state

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

			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.IntValue())
				}
			}

			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.NewString(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.IntValue())
				}
			}

			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.NewString(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.IntValue() <= 0 {
				return errors.NewError("randbelow requires a positive upper bound")
			}

			result, err := rand.Int(rand.Reader, big.NewInt(n.IntValue()))
			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.IntValue() < 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.IntValue())))
			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.StringValue()) == 0 {
					return errors.NewError("cannot choose from empty sequence")
				}
				idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(str.StringValue()))))
				if err != nil {
					return errors.NewError("failed to generate random index: %s", err.Error())
				}
				return object.NewString(string(str.StringValue()[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")
			}

			return object.NewBoolean(stdlib.CompareDigest(a.StringValue(), b.StringValue()))
		},
		HelpText: `compare_digest(a, b) - Compare two strings using constant-time comparison

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

Parameters:
  a - First string
  b - Second string

Returns: True if strings are equal, False otherwise

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

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

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

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

				shell := false
				if sh, exists := kwargs.Kwargs["shell"]; exists {
					if b, ok := sh.(*object.Boolean); ok {
						shell = b.BoolValue()
					}
				}
				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.BoolValue()
				}
			}
			if sh, exists := kwargs.Kwargs["shell"]; exists {
				if b, ok := sh.(*object.Boolean); ok {
					shell = b.BoolValue()
				}
			}
			if wd, exists := kwargs.Kwargs["cwd"]; exists {
				if s, ok := wd.(*object.String); ok {
					cwd = s.StringValue()
				}
			}
			if to, exists := kwargs.Kwargs["timeout"]; exists {
				if f, ok := to.(*object.Float); ok {
					timeout = f.FloatValue()
				} else if i, ok := to.(*object.Integer); ok {
					timeout = float64(i.IntValue())
				}
			}
			if ch, exists := kwargs.Kwargs["check"]; exists {
				if b, ok := ch.(*object.Boolean); ok {
					check = b.BoolValue()
				}
			}
			if txt, exists := kwargs.Kwargs["text"]; exists {
				if b, ok := txt.(*object.Boolean); ok {
					text = b.BoolValue()
				}
			}
			if enc, exists := kwargs.Kwargs["encoding"]; exists {
				if s, ok := enc.(*object.String); ok {
					encoding = s.StringValue()
				}
			}
			if inp, exists := kwargs.Kwargs["input"]; exists {
				if s, ok := inp.(*object.String); ok {
					inputData = s.StringValue()
				}
			}
			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.StringValue()
						}
					}
				}
			}

			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

			object.RunBlocking(ctx, func() {
				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.NewInstanceWithFields(CompletedProcessClass, map[string]object.Object{
				"args":       &object.List{Elements: make([]object.Object, len(cmdArgs))},
				"returncode": object.NewInteger(int64(returncode)),
				"stdout":     object.NewString(stdoutStr),
				"stderr":     object.NewString(stderrStr),
			})
			for i, arg := range cmdArgs {
				instance.Field("args").(*object.List).Elements[i] = object.NewString(arg)
			}

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

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

Runs a command and returns a CompletedProcess instance.

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

Returns:
  CompletedProcess instance with args, returncode, stdout, stderr`,
	},
}, map[string]object.Object{}, "Subprocess library for running external commands")
View Source
var SyncSubLibrary = object.NewLibrary(RuntimeSyncLibraryName, 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 {

							object.RunBlocking(ctx, func() { wg.wg.Wait() })
							return &object.Null{}
						},
						HelpText: "wait() - Block until counter reaches zero",
					},
				},
				HelpText: "WaitGroup - Go-style synchronization primitive",
			}
		},
		HelpText: `WaitGroup(name) - Get or create a named wait group

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

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

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

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

    wg.wait()`,
	},

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    def increment(current):
        return current + 1

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

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

Parses a TOML string and returns the corresponding Scriptling object.

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

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

Converts a Scriptling object to a TOML formatted string.

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

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

TOMLLibrary provides TOML parsing and generation functionality

View Source
var TemplateHTMLLibrary = object.NewLibrary(TemplateHTMLLibraryName, map[string]*object.Builtin{
	"Set": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 0); err != nil {
				return err
			}
			left, errObj := kwargs.GetString("left", "")
			if errObj != nil {
				return errObj
			}
			right, errObj := kwargs.GetString("right", "")
			if errObj != nil {
				return errObj
			}
			t := htmltemplate.New("").Delims(left, right)
			return newSetInstance(&parsedTemplateSet{html: t})
		},
		HelpText: `Set(left="", right="") - Create a new HTML template set (uses html/template with auto-escaping)

Parameters:
  left (str, optional): Left action delimiter, default "{{"
  right (str, optional): Right action delimiter, default "}}"

Returns:
  Set: A template set with add(source) and render([name,] data) methods

Example:
  import scriptling.template.html as html

  # Simple template
  tmpl = html.Set()
  tmpl.add("Hello, {{.Name}}!")
  print(tmpl.render({"Name": "Alice"}))

  # With partials
  tmpl = html.Set()
  tmpl.add('{{define "header"}}<h1>{{.Title}}</h1>{{end}}')
  tmpl.add('{{define "page"}}{{template "header" .}}<p>{{.Body}}</p>{{end}}')
  print(tmpl.render("page", {"Title": "Home", "Body": "Welcome"}))

  # Custom delimiters, e.g. {% %} to avoid clashing with literal {{ }}
  tmpl = html.Set(left="{%", right="%}")
  tmpl.add("<p>{%.Name%}</p>")`,
	},
}, map[string]object.Object{}, "Go html/template rendering with automatic HTML escaping")

TemplateHTMLLibrary provides html/template rendering with automatic HTML escaping

View Source
var TemplateSetClass = &object.Class{
	Name: "Set",
	Methods: map[string]object.Object{
		"add": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 2); err != nil {
					return err
				}
				self, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewError("add() called on non-Set object")
				}
				src, err := args[1].AsString()
				if err != nil {
					return err
				}
				pt := getTemplateSet(self)
				if pt == nil {
					return errors.NewError("Set not initialised")
				}
				if pt.html != nil {
					if _, parseErr := pt.html.Parse(src); parseErr != nil {
						return errors.NewError("template parse error: %s", parseErr.Error())
					}
				} else {
					if _, parseErr := pt.text.Parse(src); parseErr != nil {
						return errors.NewError("template parse error: %s", parseErr.Error())
					}
				}
				return &object.Null{}
			},
			HelpText: `add(source) - Add a template source to the set

Parameters:
  source (string): Template source, may contain {{define "name"}}...{{end}} blocks

Example:
  tmpl = html.Set()
  tmpl.add('{{define "header"}}<h1>{{.Title}}</h1>{{end}}')
  tmpl.add('{{define "page"}}{{template "header" .}}<p>{{.Body}}</p>{{end}}')`,
		},
		"render": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.RangeArgs(args, 1, 3); err != nil {
					return err
				}
				self, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewError("render() called on non-Set object")
				}

				// render(data) or render(name, data)
				var name string
				var data object.Object = &object.Null{}
				switch len(args) {
				case 2:
					data = args[1]
				case 3:
					n, err := args[1].AsString()
					if err != nil {
						return err
					}
					name = n
					data = args[2]
				}

				pt := getTemplateSet(self)
				if pt == nil {
					return errors.NewError("Set not initialised")
				}

				result, execErr := renderTemplateSet(pt, name, conversion.ToGo(data))
				if execErr != nil {
					return errors.NewError("template render error: %s", execErr.Error())
				}
				return object.NewString(result)
			},
			HelpText: `render(data) or render(name, data) - Render a template from the set

Parameters:
  name (string, optional): Name of the template to render (from {{define "name"}})
  data (dict): Template data

Returns:
  string: Rendered output

Example:
  # Anonymous / single template
  tmpl.render({"Name": "Alice"})

  # Named template
  tmpl.render("page", {"Title": "Home", "Body": "Welcome"})`,
		},
	},
}

TemplateSetClass is the class for Set objects

View Source
var TemplateTextLibrary = object.NewLibrary(TemplateTextLibraryName, map[string]*object.Builtin{
	"Set": {
		Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
			if err := errors.ExactArgs(args, 0); err != nil {
				return err
			}
			left, errObj := kwargs.GetString("left", "")
			if errObj != nil {
				return errObj
			}
			right, errObj := kwargs.GetString("right", "")
			if errObj != nil {
				return errObj
			}
			t := texttemplate.New("").Delims(left, right)
			return newSetInstance(&parsedTemplateSet{text: t})
		},
		HelpText: `Set(left="", right="") - Create a new text template set (uses text/template, no HTML escaping)

Parameters:
  left (str, optional): Left action delimiter, default "{{"
  right (str, optional): Right action delimiter, default "}}"

Returns:
  Set: A template set with add(source) and render([name,] data) methods

Example:
  import scriptling.template.text as text

  # Simple template
  tmpl = text.Set()
  tmpl.add("Hello, {{.Name}}!")
  print(tmpl.render({"Name": "Alice"}))

  # With partials
  tmpl = text.Set()
  tmpl.add('{{define "greeting"}}Hello, {{.Name}}!{{end}}')
  tmpl.add('{{define "email"}}{{template "greeting" .}}\n\nYour order is ready.{{end}}')
  print(tmpl.render("email", {"Name": "Alice"}))

  # Custom delimiters, e.g. {% %} to avoid clashing with literal {{ }}
  tmpl = text.Set(left="{%", right="%}")
  tmpl.add("Hello, {%.Name%}!")`,
	},
}, map[string]object.Object{}, "Go text/template rendering with no escaping")

TemplateTextLibrary provides text/template rendering with no escaping

View Source
var WaitForLibrary = newWaitForLibrary(nil)

WaitForLibrary is the wait_for library (no network policy)

View Source
var WebSocketClientClass = &object.Class{
	Name: "WebSocketClient",
	Methods: map[string]object.Object{
		"connected": &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("connected() called on non-WebSocketClient object")
				}

				conn := getWSConnFromInstance(instance)
				if conn == nil {
					return object.NewBoolean(false)
				}
				return object.NewBoolean(conn.IsConnected())
			},
			HelpText: `connected() - Check if the WebSocket connection is still open

Returns True if connected, False otherwise.`,
		},
		"receive": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.MinArgs(args, 1); err != nil {
					return err
				}
				instance, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewError("receive() called on non-WebSocketClient object")
				}

				timeout := 30.0
				if t := kwargs.Get("timeout"); t != nil {
					if timeoutFloat, e := t.AsFloat(); e == nil {
						timeout = timeoutFloat
					}
				}

				conn := getWSConnFromInstance(instance)
				if conn == nil {
					return &object.Null{}
				}

				msgType, data, err := conn.ReadWithTimeout(time.Duration(timeout * float64(time.Second)))
				if err != nil || data == nil {
					return &object.Null{}
				}

				if msgType == websocket.TextMessage {
					return object.NewString(string(data))
				}

				elements := make([]object.Object, len(data))
				for i, b := range data {
					elements[i] = object.NewInteger(int64(b))
				}
				return &object.List{Elements: elements}
			},
			HelpText: `receive(timeout=30) - Receive a message from the WebSocket

Parameters:
  timeout (number, optional): Timeout in seconds (default: 30)

Returns:
  string for text messages, list of bytes for binary, or None on timeout/disconnect`,
		},
		"send": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.MinArgs(args, 2); err != nil {
					return err
				}
				instance, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewError("send() called on non-WebSocketClient object")
				}

				msg := args[1]
				var data []byte

				if dict, ok := msg.(*object.Dict); ok {
					jsonData, jsonErr := json.Marshal(conversion.ToGo(dict))
					if jsonErr != nil {
						return errors.NewError("failed to encode JSON: %s", jsonErr.Error())
					}
					data = jsonData
				} else if str, ok := msg.(*object.String); ok {
					data = []byte(str.StringValue())
				} else {
					strVal, coerceErr := object.CoerceWireString(msg)
					if coerceErr != nil {
						return errors.NewError("message must be string or dict")
					}
					data = []byte(strVal)
				}

				conn := getWSConnFromInstance(instance)
				if conn == nil {
					return errors.NewError("connection closed")
				}

				if writeErr := conn.WriteMessage(websocket.TextMessage, data); writeErr != nil {
					return errors.NewError("send failed: %s", writeErr.Error())
				}
				return &object.Null{}
			},
			HelpText: `send(message) - Send a message to the WebSocket client

Parameters:
  message (string or dict): Message to send. Dicts are automatically JSON encoded.`,
		},
		"send_binary": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.MinArgs(args, 2); err != nil {
					return err
				}
				instance, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewError("send_binary() called on non-WebSocketClient object")
				}

				list, ok := args[1].(*object.List)
				if !ok {
					return errors.NewError("send_binary requires a list of bytes")
				}

				data := make([]byte, len(list.Elements))
				for i, elem := range list.Elements {
					b, e := elem.AsInt()
					if e != nil || b < 0 || b > 255 {
						return errors.NewError("send_binary requires list of bytes (0-255)")
					}
					data[i] = byte(b)
				}

				conn := getWSConnFromInstance(instance)
				if conn == nil {
					return errors.NewError("connection closed")
				}

				if writeErr := conn.WriteMessage(websocket.BinaryMessage, data); writeErr != nil {
					return errors.NewError("send_binary failed: %s", writeErr.Error())
				}
				return &object.Null{}
			},
			HelpText: `send_binary(data) - Send binary data to the WebSocket client

Parameters:
  data (list): List of byte values (0-255)`,
		},
		"close": &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("close() called on non-WebSocketClient object")
				}

				conn := getWSConnFromInstance(instance)
				if conn != nil {
					conn.Close()
				}
				return &object.Null{}
			},
			HelpText: `close() - Close the WebSocket connection`,
		},
	},
}

WebSocketClientClass is the class for WebSocket client objects passed to handlers

View Source
var WebSocketLibrary = newWebSocketLibrary(nil)

WebSocketLibrary is the WebSocket client library (no network policy)

View Source
var YAMLLibrary = object.NewLibrary(YAMLLibraryName, map[string]*object.Builtin{
	"load": {
		Fn: yamlLoadFunc,
		HelpText: `load(yaml_string) - Parse YAML string (deprecated, use safe_load)

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

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

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

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

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

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

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

Safely converts a Scriptling object to a YAML string.

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

YAMLLibrary provides YAML parsing and generation functionality

Functions

func CloseKVStore added in v0.2.22

func CloseKVStore()

CloseKVStore closes the system-wide default KV store.

func CreateJSONRPCErrorInstance added in v0.12.0

func CreateJSONRPCErrorInstance(code int64, message string, data object.Object) *object.Instance

CreateJSONRPCErrorInstance creates a JSON-RPC error object from Go code.

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 CreateWebSocketClientInstance added in v0.4.1

func CreateWebSocketClientInstance(conn *WebSocketServerConn) *object.Instance

CreateWebSocketClientInstance creates a new WebSocketClient instance

func EditFile added in v0.17.1

func EditFile(ctx context.Context, path, search, replace string) (int, error)

EditFile performs a targeted search-and-replace on a single file: it finds the exact `search` text, verifies it appears exactly once, and replaces it with `replace`. The modification is written atomically (temp file + rename), matching sed's in-place edit semantics.

Unlike SedReplace (which replaces every occurrence line-by-line), EditFile operates on the full file content and requires the match to be unique — the gold standard for coding-agent edits where "replace all" is dangerous.

search and replace may span multiple lines. Returns the number of bytes written.

func Find added in v0.17.1

func Find(ctx context.Context, root string, opts FindOptions) ([]string, error)

Find returns the paths under root that match the given filters. It uses the same concurrent walker as scriptling.find. Paths are returned in arbitrary order. The root itself is never included in the result.

func InitKVStore added in v0.2.22

func InitKVStore(path string) error

InitKVStore initializes the system-wide default KV store. If path is empty, the store operates in memory-only mode.

func IsJSONRPCError added in v0.12.0

func IsJSONRPCError(obj object.Object) bool

IsJSONRPCError reports whether obj is a JSONRPCError instance.

func KVStoreDB added in v0.2.23

func KVStoreDB(store object.Object) *snapshotkv.DB

KVStoreDB returns the underlying snapshotkv.DB for a kv store object, or nil if the object is not a kv store.

func NewCsvLibrary added in v0.17.0

func NewCsvLibrary() *object.Library

func NewFindLibrary added in v0.17.0

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

NewFindLibrary creates a new scriptling.find library with the given configuration.

func NewGlobLibrary

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

NewGlobLibrary creates a new Glob library with the given configuration.

func NewGrepLibrary added in v0.5.7

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

NewGrepLibrary creates a new scriptling.grep library with the given configuration.

func NewInputBuiltin added in v0.2.6

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

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

func NewKVSubLibrary added in v0.2.22

func NewKVSubLibrary() *object.Library

NewKVSubLibrary builds the kv sub-library with no path restrictions. Must be called after InitKVStore so RuntimeState.KVDB is set.

func NewKVSubLibraryWithSecurity added in v0.2.23

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

NewKVSubLibraryWithSecurity builds the kv sub-library restricted to allowedPaths. In-memory stores are always permitted. If allowedPaths is nil, all paths are allowed. If allowedPaths is an empty slice, all filesystem paths are denied. Must be called after InitKVStore so RuntimeState.KVDB is set.

func NewOSLibrary

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

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

func NewPathlibLibrary

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

NewPathlibLibrary creates a new Pathlib library with the given configuration.

func NewSandboxLibrary added in v0.1.1

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

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

func NewSecretLibrary added in v0.5.6

func NewSecretLibrary(registry *secretprovider.Registry) *object.Library

NewSecretLibrary creates the scriptling.secret library.

func NewSedLibrary added in v0.5.8

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

NewSedLibrary creates a new scriptling.sed library with the given configuration.

func NewShlexLibrary added in v0.17.0

func NewShlexLibrary() *object.Library

NewShlexLibrary creates a new shlex library.

func NewShutilLibrary added in v0.17.0

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

NewShutilLibrary creates a new shutil library with the given configuration.

func NewSysLibrary

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

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

func NewTarfileLibrary added in v0.17.0

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

func NewTempfileLibrary added in v0.17.0

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

NewTempfileLibrary creates a new tempfile library with the given configuration.

func NewXmlLibrary added in v0.17.0

func NewXmlLibrary() *object.Library

func NewZipfileLibrary added in v0.17.0

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

func RegisterCleanup added in v0.2.22

func RegisterCleanup(fn func())

RegisterCleanup registers a function to be called during ResetRuntime. Libraries use this to clean up their own state without creating dependencies between packages.

func RegisterCsvLibrary added in v0.17.0

func RegisterCsvLibrary(registrar object.LibraryRegistrar)

RegisterCsvLibrary registers the scriptling.csv library.

func RegisterFSLibrary added in v0.6.2

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

func RegisterFindLibrary added in v0.17.0

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

RegisterFindLibrary registers the scriptling.find library with a Scriptling instance. If allowedPaths is nil, all paths are allowed. If non-nil, all find operations are restricted to those directories (same semantics as RegisterGrepLibrary).

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 RegisterGrepLibrary added in v0.5.7

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

RegisterGrepLibrary registers the scriptling.grep library with a Scriptling instance. If allowedPaths is nil, all paths are allowed. If non-nil, operations are restricted to those directories (same semantics as RegisterOSLibrary).

func RegisterHTMLParserLibrary

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

func RegisterLoggingLibrary

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

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

func RegisterLoggingLibraryDefault

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

RegisterLoggingLibraryDefault registers the logging library with default configuration

func RegisterMarkdownLibrary added in v0.11.3

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

func RegisterOSLibrary

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

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

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

Example:

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

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

func RegisterPathlibLibrary

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

RegisterPathlibLibrary registers the pathlib library with a Scriptling instance.

func RegisterRequestsLibrary

func RegisterRequestsLibrary(registrar interface{ RegisterLibrary(*object.Library) }, cfg ...*netsecurity.Config)

func RegisterRuntimeHTTPLibrary

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

func RegisterRuntimeJSONRPCLibrary added in v0.12.0

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

RegisterRuntimeJSONRPCLibrary registers only the jsonrpc sub-library.

func RegisterRuntimeKVLibrary

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

func RegisterRuntimeKVLibraryWithSecurity added in v0.2.23

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

RegisterRuntimeKVLibraryWithSecurity registers the kv library restricted to allowedPaths. In-memory stores are always permitted regardless of allowedPaths. If allowedPaths is nil, all paths are allowed. If empty slice, all filesystem paths are denied.

func RegisterRuntimeLibrary

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

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

func RegisterRuntimeLibraryAll

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

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

func RegisterRuntimeMCPLibrary added in v0.18.0

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

RegisterRuntimeMCPLibrary registers only the runtime.mcp sub-library.

func RegisterRuntimePluginLibrary added in v0.15.0

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

RegisterRuntimePluginLibrary registers the plugin sub-library and exposes it as runtime.plugin on the parent library so that `import scriptling.runtime as rt; rt.plugin.serve(...)` works. Call this AFTER RegisterRuntimeLibraryAll. Intentionally not included in RegisterRuntimeLibraryAll — available only for the agent variant.

func RegisterRuntimeSandboxLibrary

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

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

func RegisterRuntimeSyncLibrary

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

func RegisterSecretLibrary added in v0.5.6

func RegisterSecretLibrary(registrar interface{ RegisterLibrary(*object.Library) }, registry *secretprovider.Registry)

RegisterSecretLibrary registers the provider-agnostic secret access library.

func RegisterSecretsLibrary

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

func RegisterSedLibrary added in v0.5.8

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

RegisterSedLibrary registers the scriptling.sed library with a Scriptling instance. If allowedPaths is nil, all paths are allowed. If non-nil, operations are restricted to those directories (same semantics as RegisterOSLibrary).

func RegisterShlexLibrary added in v0.17.0

func RegisterShlexLibrary(registrar object.LibraryRegistrar)

RegisterShlexLibrary registers the shlex library with a Scriptling instance.

func RegisterShutilLibrary added in v0.17.0

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

RegisterShutilLibrary registers the shutil library with a Scriptling instance.

func RegisterSubprocessLibrary

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

func RegisterSysLibrary

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

func RegisterTOMLLibrary

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

func RegisterTarfileLibrary added in v0.17.0

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

func RegisterTempfileLibrary added in v0.17.0

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

RegisterTempfileLibrary registers the tempfile library with a Scriptling instance.

func RegisterTemplateHTMLLibrary added in v0.6.0

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

func RegisterTemplateTextLibrary added in v0.6.0

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

func RegisterWaitForLibrary

func RegisterWaitForLibrary(registrar interface{ RegisterLibrary(*object.Library) }, cfg ...*netsecurity.Config)

func RegisterWebSocketLibrary added in v0.4.1

func RegisterWebSocketLibrary(registrar interface{ RegisterLibrary(*object.Library) }, cfg ...*netsecurity.Config)

RegisterWebSocketLibrary registers the WebSocket client library. An optional network policy guards the outbound dial.

func RegisterXmlLibrary added in v0.17.0

func RegisterXmlLibrary(registrar object.LibraryRegistrar)

RegisterXmlLibrary registers the scriptling.xml library.

func RegisterYAMLLibrary

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

func RegisterZipfileLibrary added in v0.17.0

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

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 SedReplace added in v0.17.1

func SedReplace(ctx context.Context, old, replacement, path string, opts SedOptions) (int64, error)

SedReplace replaces every occurrence of old with replacement in the file (or every matching file under the directory) at path. old is matched literally, not as a regular expression. Files are edited in place using an atomic temp-file + rename. The return value is the number of files modified.

func SedReplacePattern added in v0.17.1

func SedReplacePattern(ctx context.Context, pattern, replacement, path string, opts SedOptions) (int64, error)

SedReplacePattern is SedReplace with old interpreted as a regular expression. Capture groups may be referenced in replacement as ${1}, ${2}, or ${name}.

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

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

CompletedProcess represents the result of a subprocess.run call

func (*CompletedProcess) AsBool

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

func (*CompletedProcess) AsDict

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

func (*CompletedProcess) AsFloat

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

func (*CompletedProcess) AsInt

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

func (*CompletedProcess) AsList

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

func (*CompletedProcess) AsString

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

func (*CompletedProcess) Inspect

func (cp *CompletedProcess) Inspect() string

func (*CompletedProcess) Type

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

type ExtractMatch added in v0.17.1

type ExtractMatch struct {
	File   string
	Line   int
	Text   string
	Groups []string
}

ExtractMatch is a single regex match with its capture groups, returned by SedExtract.

func SedExtract added in v0.17.1

func SedExtract(ctx context.Context, pattern, path string, opts SedOptions) ([]ExtractMatch, error)

SedExtract returns every match of pattern (a regular expression with capture groups) found in the file or directory at path. The result includes the captured groups for each match.

type FindEntry added in v0.17.7

type FindEntry struct {
	Path       string
	Size       int64
	Mtime      time.Time
	IsDir      bool
	Hash       uint64 // crc64 of file content when FindOptions.IncludeHash is set; 0 otherwise
	LinkTarget string // symlink target when the entry is a symlink; empty for regular files/dirs
	FilePerm   int    // file permission bits; populated when FindOptions.IncludeMetadata is set
}

FindEntry is a single matching entry returned by FindEntries, carrying the metadata required to decide whether the entry has changed without re-reading it. Callers comparing two trees (e.g. a sync tool diffing local and remote) can rely on Size+Mtime alone for the common case.

func FindEntries added in v0.17.7

func FindEntries(ctx context.Context, root string, opts FindOptions) ([]FindEntry, error)

FindEntries is like Find but returns FindEntry records with size, mtime, and type per match. Every matching entry is stat'd so the caller can compare trees without re-reading the bytes. Use Find instead when only the path strings are needed — Find skips the stat in the no-filter common case.

Like Find, the root itself is never included in the result, and paths are returned in arbitrary order.

type FindOptions added in v0.17.1

type FindOptions struct {
	Recursive       *bool
	Type            string
	Name            string
	MtimeMin        *float64
	MtimeMax        *float64
	SizeMin         *int64
	SizeMax         *int64
	IncludeHidden   bool
	FollowLinks     bool
	MaxDepth        int
	AllowedPaths    []string
	IncludeHash     bool // when true, every entry's file content is crc64-hashed
	IncludeSymlinks bool // when true, symlink entries are yielded with their target in LinkTarget
	IncludeMetadata bool // when true, file_perm is populated
}

FindOptions controls a find search.

Recursive is a pointer so that the zero value (nil) preserves scriptling's default of descending into subdirectories. Pass a pointer to false to keep the search non-recursive.

Type selects "any" (the zero value), "file", or "dir".

MtimeMin/MtimeMax and SizeMin/SizeMax are pointers so that a zero value is not confused with the valid bound 0; nil means the filter is inactive.

MaxDepth of 0 means unlimited.

type GlobLibraryInstance

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

GlobLibraryInstance holds the configured Glob library instance

type GrepMatch added in v0.17.1

type GrepMatch struct {
	File string
	Line int
	Text string
}

GrepMatch is a single matching line.

func Grep added in v0.17.1

func Grep(ctx context.Context, needle, path string, opts GrepOptions) ([]GrepMatch, error)

Grep searches path for needle. When path is a directory the search runs concurrently over its files using the same bounded worker pool as scriptling.grep, respecting opts.Recursive / opts.Glob / opts.MaxSize.

needle is interpreted as a regular expression unless opts.Literal is true. Matches are returned in arbitrary order.

type GrepOptions added in v0.17.1

type GrepOptions struct {
	Literal      bool
	Recursive    bool
	IgnoreCase   bool
	FollowLinks  bool
	Glob         string
	MaxSize      int64
	AllowedPaths []string
}

GrepOptions controls a Grep search.

Literal selects literal-string matching (scriptling.grep.string) when true, versus regular-expression matching (scriptling.grep.pattern) when false.

MaxSize skips files larger than this many bytes. The zero value applies the scriptling default of 1 MiB; a negative value disables the limit.

AllowedPaths, when non-nil, restricts every searched path to the listed absolute directories (nil = no restriction, matching the interpreter default).

type PathlibLibraryInstance

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

PathlibLibraryInstance holds the configured Pathlib library instance

type Promise

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

Promise represents an async operation result

type RouteInfo

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

RouteInfo stores information about a registered route

type RuntimeAtomic

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

RuntimeAtomic is a named atomic counter

type RuntimeQueue

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

RuntimeQueue is a named thread-safe queue

type RuntimeShared

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

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

type RuntimeWaitGroup

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

RuntimeWaitGroup is a named wait group

type SandboxFactory

type SandboxFactory func() SandboxInstance

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

func GetSandboxFactory added in v0.1.1

func GetSandboxFactory() SandboxFactory

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

type SandboxInstance

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

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

type SedOptions added in v0.17.1

type SedOptions struct {
	Recursive    bool
	IgnoreCase   bool
	FollowLinks  bool
	Glob         string
	MaxSize      int64
	AllowedPaths []string
}

SedOptions controls a sed replace or extract operation. See GrepOptions for the meaning of MaxSize and AllowedPaths; the semantics are identical.

type WebSocketClientConn added in v0.4.1

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

WebSocketClientConn wraps a websocket.Conn with thread-safe access

func NewWebSocketClientConn added in v0.4.1

func NewWebSocketClientConn(conn *websocket.Conn) *WebSocketClientConn

NewWebSocketClientConn creates a new wrapped WebSocket connection

func (*WebSocketClientConn) Close added in v0.4.1

func (c *WebSocketClientConn) Close() error

Close closes the connection

func (*WebSocketClientConn) ClosedChan added in v0.4.1

func (c *WebSocketClientConn) ClosedChan() <-chan struct{}

ClosedChan returns a channel that closes when the connection closes

func (*WebSocketClientConn) IsConnected added in v0.4.1

func (c *WebSocketClientConn) IsConnected() bool

IsConnected returns whether the connection is still open

func (*WebSocketClientConn) ReadWithTimeout added in v0.4.1

func (c *WebSocketClientConn) ReadWithTimeout(timeout time.Duration) (int, []byte, error)

ReadWithTimeout reads a message with a timeout Returns messageType, data, error. On timeout, returns 0, nil, nil

func (*WebSocketClientConn) RemoteAddr added in v0.4.1

func (c *WebSocketClientConn) RemoteAddr() string

RemoteAddr returns the remote address

func (*WebSocketClientConn) WriteMessage added in v0.4.1

func (c *WebSocketClientConn) WriteMessage(msgType int, data []byte) error

WriteMessage sends a message

type WebSocketRouteInfo added in v0.4.1

type WebSocketRouteInfo struct {
	Handler string // "library.function" to call for each connection
}

WebSocketRouteInfo stores information about a registered WebSocket route

type WebSocketServerConn added in v0.4.1

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

WebSocketServerConn wraps a server-side WebSocket connection

func NewWebSocketServerConn added in v0.4.1

func NewWebSocketServerConn(conn *websocket.Conn, id string) *WebSocketServerConn

NewWebSocketServerConn creates a new server WebSocket connection wrapper

func (*WebSocketServerConn) Close added in v0.4.1

func (c *WebSocketServerConn) Close() error

Close closes the connection

func (*WebSocketServerConn) ClosedChan added in v0.4.1

func (c *WebSocketServerConn) ClosedChan() <-chan struct{}

ClosedChan returns a channel that closes when the connection closes

func (*WebSocketServerConn) ID added in v0.4.1

func (c *WebSocketServerConn) ID() string

ID returns the connection ID

func (*WebSocketServerConn) IsConnected added in v0.4.1

func (c *WebSocketServerConn) IsConnected() bool

IsConnected returns whether the connection is still open

func (*WebSocketServerConn) ReadWithTimeout added in v0.4.1

func (c *WebSocketServerConn) ReadWithTimeout(timeout time.Duration) (int, []byte, error)

ReadWithTimeout reads a message with timeout. Returns messageType, data, error. On timeout, returns 0, nil, nil

func (*WebSocketServerConn) RemoteAddr added in v0.4.1

func (c *WebSocketServerConn) RemoteAddr() string

RemoteAddr returns the remote address

func (*WebSocketServerConn) WriteMessage added in v0.4.1

func (c *WebSocketServerConn) WriteMessage(msgType int, data []byte) error

WriteMessage sends a message

Directories

Path Synopsis
ai
Example demonstrating the module-level console API and how background tasks can access the shared TUI.
Example demonstrating the module-level console API and how background tasks can access the shared TUI.
messaging
net
Package netsecurity restricts outbound network access for script-facing libraries (requests, wait_for, websocket).
Package netsecurity restricts outbound network access for script-facing libraries (requests, wait_for, websocket).
Package nomad implements the scriptling.nomad extended library: a thin client over the HashiCorp Nomad HTTP API covering CSI volumes and jobs.
Package nomad implements the scriptling.nomad extended library: a thin client over the HashiCorp Nomad HTTP API covering CSI volumes and jobs.
provision

Jump to

Keyboard shortcuts

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