Add archive helpers and harden task runtime
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
Markup Functions
|
||||
markdown_to_ast
|
||||
markdown_to_html
|
||||
xml_decode
|
||||
xml_encode
|
||||
yaml_decode
|
||||
yaml_encode
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
Output / Invocation Functions
|
||||
|
||||
1_RENDER
|
||||
1_CLI
|
||||
cli_input
|
||||
cli_arg
|
||||
unit_call
|
||||
component
|
||||
component_exists
|
||||
|
||||
@@ -16,3 +16,9 @@ cwd_set
|
||||
shell_escape
|
||||
shell_exec
|
||||
file_unlink
|
||||
zip_create
|
||||
zip_list
|
||||
zip_read
|
||||
zip_extract
|
||||
gz_compress
|
||||
gz_uncompress
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
:title
|
||||
CLI
|
||||
|
||||
:sig
|
||||
CLI(Request& context)
|
||||
|
||||
:see
|
||||
>1_RENDER
|
||||
>1_COMPONENT
|
||||
>1_WS
|
||||
>1_INIT
|
||||
>1_ONCE
|
||||
>unit_call
|
||||
>cli_input
|
||||
>cli_arg
|
||||
|
||||
:content
|
||||
Defines a local command-line entrypoint for a UCE unit.
|
||||
|
||||
`CLI(Request& context)` is invoked only through the local UCE CLI Unix socket, not through ordinary public HTTP requests. This lets web apps keep test runners, migrations, maintenance tasks, and admin tooling beside the rest of their UCE units while still separating those commands from browser-facing `RENDER()` routes.
|
||||
|
||||
The default CLI socket path is `/run/uce/cli.sock` and is configured with `CLI_SOCKET_PATH`.
|
||||
|
||||
Example convenience script usage:
|
||||
|
||||
```sh
|
||||
scripts/uce-cli /tests/cli.uce
|
||||
scripts/uce-cli /tests/cli.uce action=echo message=hello
|
||||
scripts/uce-cli --json '{"action":"echo","message":"hello"}' /tests/cli.uce
|
||||
```
|
||||
|
||||
Equivalent curl probe:
|
||||
|
||||
```sh
|
||||
curl --unix-socket /run/uce/cli.sock http://localhost/tests/cli.uce
|
||||
```
|
||||
|
||||
For structured commands, prefer JSON POST bodies. The `scripts/uce-cli` helper sends `key=value` parameters as JSON POST by default, while still allowing `--get` for simple query-string probes.
|
||||
|
||||
Inside the handler, the usual `Request& context` fields are available:
|
||||
|
||||
- `context.get` for command query parameters
|
||||
- `context.post` and `context.in` for POST bodies
|
||||
- `context.params["UCE_CLI"] == "1"` for CLI socket dispatch
|
||||
- `context.params["SCRIPT_FILENAME"]` for the invoked unit file
|
||||
|
||||
CLI responses default to `text/plain; charset=utf-8`, but the handler may set headers and status explicitly.
|
||||
|
||||
Use `cli_input(context)` to merge query parameters, form parameters, and JSON POST fields into one `DTree`.
|
||||
|
||||
```cpp
|
||||
CLI(Request& context)
|
||||
{
|
||||
DTree input = cli_input(context);
|
||||
String action = first(input["action"].to_string(), "ping");
|
||||
if(action == "ping")
|
||||
{
|
||||
print("ok\n");
|
||||
return;
|
||||
}
|
||||
|
||||
context.set_status(400, "Bad Command");
|
||||
print("unknown action: ", action, "\n");
|
||||
}
|
||||
```
|
||||
|
||||
`ONCE(Request& context)` runs before `CLI()` in the same way it runs before render and component entrypoints. `INIT(Request& context)` runs when the unit is loaded into a worker.
|
||||
@@ -25,7 +25,7 @@ The template rewriting implementation lives in `src/lib/compiler-parser.cpp`, wi
|
||||
- Inside a literal block, `<?= expression ?>` emits `print(html_escape(expression));`.
|
||||
- Inside a literal block, `<?: expression ?>` emits `print(expression);` without HTML escaping.
|
||||
- `#load "other.uce"` injects another UCE unit at compile time.
|
||||
- `RENDER(Request& context)`, `COMPONENT(Request& context)`, `ONCE(Request& context)`, `INIT(Request& context)`, and `WS(Request& context)` are normal C++ macros from `src/lib/compiler.h`.
|
||||
- `RENDER(Request& context)`, `COMPONENT(Request& context)`, `CLI(Request& context)`, `ONCE(Request& context)`, `INIT(Request& context)`, and `WS(Request& context)` are normal C++ macros from `src/lib/compiler.h`.
|
||||
- `COMPONENT:NAME(Request& context)` is rewritten by the custom pass into an exported named component handler.
|
||||
- `EXPORT` is also a normal C++ macro, but the custom pass additionally records exported declarations for metadata.
|
||||
|
||||
@@ -47,7 +47,7 @@ The template rewriting implementation lives in `src/lib/compiler-parser.cpp`, wi
|
||||
- The final generated source is written to `BIN_DIRECTORY + src_path + "/" + source_file + ".cpp"`.
|
||||
- `scripts/compile` then compiles that generated `.cpp` into `source_file + ".so"` with `clang++ -shared -std=c++20 ...`.
|
||||
- When a worker loads the compiled unit into memory, the runtime checks for `INIT(Request& context)` and calls it once for that worker-side load.
|
||||
- On each request, the first time a given unit is entered through `RENDER()` or any `COMPONENT...` handler, the runtime checks for `ONCE(Request& context)` and calls it before the render/component handler.
|
||||
- On each request, the first time a given unit is entered through `RENDER()`, `CLI()`, or any `COMPONENT...` handler, the runtime checks for `ONCE(Request& context)` and calls it before the selected handler.
|
||||
|
||||
## Generated Files
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
:title
|
||||
cli_arg
|
||||
|
||||
:sig
|
||||
String cli_arg(Request& context, String key, String default_value = "")
|
||||
|
||||
:see
|
||||
>1_CLI
|
||||
>cli_input
|
||||
|
||||
:content
|
||||
Reads one value from the merged `cli_input(context)` parameter tree.
|
||||
|
||||
If the key is missing, or the resolved value is empty, `default_value` is returned.
|
||||
|
||||
```cpp
|
||||
CLI(Request& context)
|
||||
{
|
||||
String action = cli_arg(context, "action", "help");
|
||||
print("action=", action, "\n");
|
||||
}
|
||||
```
|
||||
|
||||
For commands that need multiple values or typed reads, prefer calling `cli_input(context)` once and reading the returned `DTree` directly.
|
||||
@@ -0,0 +1,41 @@
|
||||
:title
|
||||
cli_input
|
||||
|
||||
:sig
|
||||
DTree cli_input(Request& context)
|
||||
|
||||
:see
|
||||
>1_CLI
|
||||
>json_decode
|
||||
>DTree
|
||||
|
||||
:content
|
||||
Returns a structured parameter tree for a `CLI(Request& context)` invocation.
|
||||
|
||||
`cli_input()` merges simple command inputs into one `DTree`:
|
||||
|
||||
1. query parameters from `context.get`
|
||||
2. form parameters from `context.post`
|
||||
3. JSON object fields from an `application/json` or `*+json` request body
|
||||
|
||||
Later sources override earlier ones, so a JSON body can override a query or form key with the same name.
|
||||
|
||||
If the JSON body is a scalar or array instead of an object, the decoded value is stored under `input["_"]`.
|
||||
|
||||
```cpp
|
||||
CLI(Request& context)
|
||||
{
|
||||
DTree input = cli_input(context);
|
||||
String action = first(input["action"].to_string(), "help");
|
||||
|
||||
if(action == "echo")
|
||||
print(input["message"].to_string(), "\n");
|
||||
}
|
||||
```
|
||||
|
||||
Convenience script usage:
|
||||
|
||||
```sh
|
||||
scripts/uce-cli /tests/cli.uce action=echo message=hello
|
||||
scripts/uce-cli --json '{"action":"echo","message":"hello"}' /tests/cli.uce
|
||||
```
|
||||
@@ -0,0 +1,24 @@
|
||||
:sig
|
||||
String gz_compress(String src)
|
||||
|
||||
:params
|
||||
src : uncompressed input bytes
|
||||
return value : gzip-format compressed bytes
|
||||
|
||||
:see
|
||||
>sys
|
||||
gz_uncompress
|
||||
zip_create
|
||||
zip_read
|
||||
|
||||
:content
|
||||
Compresses `src` and returns a gzip-format byte string.
|
||||
|
||||
The result is binary data. Store it in a file, send it with an appropriate content type/encoding, or pass it directly to `gz_uncompress()`.
|
||||
|
||||
```uce
|
||||
String compressed = gz_compress("hello\n");
|
||||
file_put_contents("/tmp/hello.txt.gz", compressed);
|
||||
```
|
||||
|
||||
UCE writes a standard gzip wrapper around a deflate stream, including CRC32 and uncompressed-size footer fields.
|
||||
@@ -0,0 +1,22 @@
|
||||
:sig
|
||||
String gz_uncompress(String compressed)
|
||||
|
||||
:params
|
||||
compressed : gzip-format compressed bytes
|
||||
return value : uncompressed bytes
|
||||
|
||||
:see
|
||||
>sys
|
||||
gz_compress
|
||||
zip_list
|
||||
zip_extract
|
||||
|
||||
:content
|
||||
Uncompresses a gzip-format byte string and returns the original content.
|
||||
|
||||
```uce
|
||||
String compressed = file_get_contents("/tmp/hello.txt.gz");
|
||||
String plain = gz_uncompress(compressed);
|
||||
```
|
||||
|
||||
`gz_uncompress()` validates the gzip header, CRC32 footer, and uncompressed-size footer. It throws a runtime error when the input is not a supported gzip stream or fails validation.
|
||||
+11
-5
@@ -1,20 +1,26 @@
|
||||
:sig
|
||||
pid_t task(String key, std::function<void()> exec_func)
|
||||
pid_t task(String key, std::function<void()> exec_func, u64 timeout = 60*10)
|
||||
|
||||
:params
|
||||
key : string uniquely identifying the task
|
||||
key : string uniquely identifying the task across the whole runtime instance
|
||||
exec_func : function to execute
|
||||
return value : the process ID of the started (or still running) task
|
||||
timeout : maximum run time in seconds; `0` disables the timeout
|
||||
return value : the process ID of the started (or still running) task, or `0` when the task could not be started
|
||||
|
||||
:see
|
||||
>task
|
||||
task_pid
|
||||
task_kill
|
||||
task_repeat
|
||||
|
||||
:content
|
||||
Starts `exec_func` in a new process and returns that process ID.
|
||||
|
||||
If a process with the same `key` is already running, `task()` does not start a second copy. Instead it returns the PID of the already-running task.
|
||||
If a process with the same `key` is already running anywhere in the runtime instance, `task()` does not start a second copy. Instead it returns the PID of the already-running task. Coordination is through a shared task status file under `BIN_DIRECTORY`, so the key applies across workers, not just the current worker process.
|
||||
|
||||
Use the `key` to make a background job idempotent across repeated calls.
|
||||
Task keys may contain ordinary user-facing text. UCE hashes the key before using it as an internal lock/status filename so slashes and other path-like characters cannot escape the task state directory.
|
||||
|
||||
`timeout` is enforced in the child process with an alarm. The default is ten minutes. Pass `0` only for tasks that are intentionally unbounded and have their own shutdown path.
|
||||
|
||||
Related:
|
||||
|
||||
|
||||
@@ -13,7 +13,9 @@ task_pid
|
||||
task_repeat
|
||||
|
||||
:content
|
||||
Wraps the standard POSIX `kill()` function.
|
||||
Wraps the standard POSIX `kill()` function for positive process IDs.
|
||||
|
||||
`task_kill()` rejects `pid <= 0` and returns `-1`, so callers cannot accidentally use POSIX process-group or broadcast semantics through this helper.
|
||||
|
||||
`sig` may be any supported POSIX signal, including values such as `SIGTERM`, `SIGKILL`, `SIGINT`, `SIGUSR1`, `SIGUSR2`, `SIGCHLD`, `SIGCONT`, and related process-control signals.
|
||||
|
||||
|
||||
@@ -9,9 +9,11 @@ return value : the process ID of the task
|
||||
>task
|
||||
|
||||
:content
|
||||
Checks whether a process with the given `key` is running and returns its PID if it is.
|
||||
Checks whether a process with the given `key` is running anywhere in the runtime instance and returns its PID if it is.
|
||||
|
||||
Returns `0` when no matching task is active.
|
||||
Returns `0` when no matching task is active or task state cannot be read safely. Stale task status files are removed when the recorded PID is no longer alive.
|
||||
|
||||
New task status records include the Linux process start tick from `/proc/<pid>/stat`, so `task_pid()` can reject a stale status file if the PID has exited and the numeric PID has since been reused by another process.
|
||||
|
||||
Related:
|
||||
|
||||
|
||||
@@ -2,21 +2,26 @@
|
||||
pid_t task_repeat(String key, f64 interval, std::function<void()> exec_func, u64 timeout = 60*10)
|
||||
|
||||
:params
|
||||
key : string uniquely identifying the task
|
||||
key : string uniquely identifying the task across the whole runtime instance
|
||||
interval : repeat interval in seconds
|
||||
exec_func : function to execute repeatedly
|
||||
timeout : optional task timeout value
|
||||
return value : the process ID of the started (or still running) task
|
||||
timeout : maximum run time in seconds; `0` disables the timeout
|
||||
return value : the process ID of the started (or still running) task, or `0` when the task could not be started
|
||||
|
||||
:see
|
||||
>task
|
||||
task
|
||||
task_pid
|
||||
task_kill
|
||||
|
||||
:content
|
||||
Starts a repeating background worker process.
|
||||
|
||||
`exec_func` runs in a loop, and the worker sleeps for `interval` seconds between executions.
|
||||
`exec_func` runs in a loop, and the worker sleeps for `interval` seconds between executions. `interval` must be greater than zero.
|
||||
|
||||
If a process with the same `key` is already running, `task_repeat()` does not start a second worker and instead returns the PID of the existing one.
|
||||
If a process with the same `key` is already running anywhere in the runtime instance, `task_repeat()` does not start a second worker and instead returns the PID of the existing one. Coordination is through the same shared task state used by `task()`.
|
||||
|
||||
`timeout` bounds the lifetime of the repeating worker. The default is ten minutes. Pass `0` only for intentionally unbounded workers with another shutdown path.
|
||||
|
||||
Related:
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
:sig
|
||||
DTree xml_decode(String s)
|
||||
|
||||
:params
|
||||
s : XML source string
|
||||
return value : element-shaped DTree
|
||||
|
||||
:see
|
||||
>markup
|
||||
xml_encode
|
||||
json_decode
|
||||
0_DTree
|
||||
String
|
||||
|
||||
:content
|
||||
Parses a simple XML document into a structured `DTree`.
|
||||
|
||||
`xml_decode()` is intentionally small. It does not validate schemas, DTDs, namespaces, or document types. It parses the first root element and returns the same structural element shape accepted by `xml_encode()`.
|
||||
|
||||
Try the live example in the [XML demo](../demo/xml.uce).
|
||||
|
||||
Return shape:
|
||||
|
||||
```text
|
||||
node["name"] = element name
|
||||
node["attrs"] = map of attributes
|
||||
node["text"] = text content when non-empty
|
||||
node["children"] = list of child element nodes
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```uce
|
||||
DTree book = xml_decode("<book id=\"b1\"><title>UCE & XML</title></book>");
|
||||
|
||||
book["name"].to_string(); // book
|
||||
book["attrs"]["id"].to_string(); // b1
|
||||
book["children"]["0"]["name"].to_string(); // title
|
||||
book["children"]["0"]["text"].to_string(); // UCE & XML
|
||||
```
|
||||
|
||||
CDATA and numeric entities are folded into text:
|
||||
|
||||
```uce
|
||||
DTree note = xml_decode("<note><![CDATA[5 < 6]]><symbol>AB</symbol></note>");
|
||||
|
||||
note["text"].to_string(); // 5 < 6
|
||||
note["children"]["0"]["text"].to_string(); // AB
|
||||
```
|
||||
|
||||
Supported parser features:
|
||||
|
||||
- elements
|
||||
- attributes with quoted values
|
||||
- self-closing tags
|
||||
- text nodes
|
||||
- XML entities such as `&`, `<`, `>`, `"`, and `'`
|
||||
- decimal and hexadecimal numeric entities
|
||||
- comments, processing instructions, and CDATA sections
|
||||
|
||||
Whitespace-only text between child elements is ignored. Mixed non-empty text is concatenated into `node["text"]`.
|
||||
|
||||
Malformed XML raises a request-visible runtime error.
|
||||
@@ -0,0 +1,76 @@
|
||||
:sig
|
||||
String xml_encode(DTree t)
|
||||
String xml_encode(DTree t, String root_name)
|
||||
|
||||
:params
|
||||
t : tree to serialize as XML
|
||||
root_name : optional element name to use when `t` is not already an element-shaped tree, defaults to `root`
|
||||
return value : XML string
|
||||
|
||||
:see
|
||||
>markup
|
||||
xml_decode
|
||||
json_encode
|
||||
0_DTree
|
||||
html_escape
|
||||
|
||||
:content
|
||||
Serializes a `DTree` into a simple XML string.
|
||||
|
||||
`xml_encode()` does not validate against a schema, DTD, or namespace rules. It is a structural converter for application data, similar in spirit to `json_encode()`.
|
||||
|
||||
Try the live example in the [XML demo](../demo/xml.uce).
|
||||
|
||||
The native element shape is:
|
||||
|
||||
```text
|
||||
node["name"] = "book"
|
||||
node["attrs"]["id"] = "b1"
|
||||
node["text"] = "optional text"
|
||||
node["children"] = list of child element nodes
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```uce
|
||||
DTree book;
|
||||
book["name"] = "book";
|
||||
book["attrs"]["id"] = "b1";
|
||||
|
||||
DTree title;
|
||||
title["name"] = "title";
|
||||
title["text"] = "UCE & XML";
|
||||
book["children"].push(title);
|
||||
|
||||
String xml = xml_encode(book);
|
||||
```
|
||||
|
||||
The result is:
|
||||
|
||||
```xml
|
||||
<book id="b1"><title>UCE & XML</title></book>
|
||||
```
|
||||
|
||||
For simple map/list/scalar trees, `xml_encode()` creates ordinary child elements:
|
||||
|
||||
```uce
|
||||
DTree payload;
|
||||
payload["title"] = "Hello";
|
||||
payload["count"] = "3";
|
||||
|
||||
String xml = xml_encode(payload, "payload");
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```xml
|
||||
<payload><count>3</count><title>Hello</title></payload>
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Element and attribute names are normalized to XML-name-safe strings when needed.
|
||||
- Text and attribute values are escaped.
|
||||
- List values use repeated `<item>` children.
|
||||
- Empty elements serialize as self-closing tags.
|
||||
- Map child order follows `DTree` map iteration order.
|
||||
@@ -0,0 +1,57 @@
|
||||
:sig
|
||||
DTree yaml_decode(String s)
|
||||
|
||||
:params
|
||||
s : YAML source string
|
||||
return value : decoded DTree
|
||||
|
||||
:see
|
||||
>markup
|
||||
yaml_encode
|
||||
json_decode
|
||||
xml_decode
|
||||
0_DTree
|
||||
|
||||
:content
|
||||
Parses a practical YAML subset into a `DTree`.
|
||||
|
||||
`yaml_decode()` is designed for concise UCE config files. It intentionally avoids full YAML schema behavior and does not support anchors, aliases, tags, directives, or complex inline collection syntax.
|
||||
|
||||
Try the live example in the [YAML demo](../demo/yaml.uce).
|
||||
|
||||
Example:
|
||||
|
||||
```uce
|
||||
String source = "app:\n"
|
||||
" name: UCE Starter\n"
|
||||
" debug: true\n"
|
||||
" port: 8080\n"
|
||||
" paths:\n"
|
||||
" - site\n"
|
||||
" - cache\n";
|
||||
|
||||
DTree cfg = yaml_decode(source);
|
||||
|
||||
cfg["app"]["name"].to_string(); // UCE Starter
|
||||
cfg["app"]["debug"].to_bool(); // true
|
||||
cfg["app"]["port"].to_s64(); // 8080
|
||||
cfg["app"]["paths"]["1"].to_string(); // cache
|
||||
```
|
||||
|
||||
Supported syntax:
|
||||
|
||||
- indentation-based maps
|
||||
- indentation-based lists
|
||||
- `key: value` map entries
|
||||
- list entries with `- value`
|
||||
- quoted strings with single or double quotes
|
||||
- booleans `true` and `false`
|
||||
- empty/null-ish values as empty strings
|
||||
- comments beginning with `#` outside quoted strings
|
||||
- literal block strings with `|`
|
||||
- folded block strings with `>`
|
||||
- optional `---` and `...` document markers
|
||||
|
||||
Numeric-looking values are stored as strings, matching `json_decode()`'s current behavior. Use `to_s64()`, `to_u64()`, or `to_f64()` when reading numeric config values.
|
||||
|
||||
Malformed input raises a request-visible `yaml_decode(): ...` runtime error.
|
||||
@@ -0,0 +1,57 @@
|
||||
:sig
|
||||
String yaml_encode(DTree t)
|
||||
|
||||
:params
|
||||
t : tree to serialize as YAML
|
||||
return value : YAML string
|
||||
|
||||
:see
|
||||
>markup
|
||||
yaml_decode
|
||||
json_encode
|
||||
xml_encode
|
||||
0_DTree
|
||||
|
||||
:content
|
||||
Serializes a `DTree` into a compact YAML string for configuration files.
|
||||
|
||||
`yaml_encode()` focuses on the ordinary data shapes UCE apps use for config: nested maps, lists, strings, booleans, and numeric `f64` values. It does not emit YAML tags, anchors, aliases, or custom schema features.
|
||||
|
||||
Try the live example in the [YAML demo](../demo/yaml.uce).
|
||||
|
||||
Example:
|
||||
|
||||
```uce
|
||||
DTree cfg;
|
||||
cfg["app"]["name"] = "UCE Starter";
|
||||
cfg["app"]["debug"].set_bool(true);
|
||||
cfg["app"]["port"] = (f64)8080;
|
||||
|
||||
DTree path;
|
||||
path = "site";
|
||||
cfg["app"]["paths"].push(path);
|
||||
path = "cache";
|
||||
cfg["app"]["paths"].push(path);
|
||||
|
||||
String yaml = yaml_encode(cfg);
|
||||
```
|
||||
|
||||
Typical result:
|
||||
|
||||
```yaml
|
||||
app:
|
||||
debug: true
|
||||
name: UCE Starter
|
||||
paths:
|
||||
- site
|
||||
- cache
|
||||
port: 8080
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Map keys are emitted in `DTree` map iteration order.
|
||||
- Strings are left plain when safe and quoted when needed.
|
||||
- Multiline strings are emitted as literal block scalars with `|`.
|
||||
- Empty strings are emitted as `""` so they are not confused with YAML null.
|
||||
- Decoded numeric-looking config values are strings unless the original `DTree` value was explicitly numeric.
|
||||
@@ -0,0 +1,39 @@
|
||||
:sig
|
||||
bool zip_create(String zip_file_name, DTree entries)
|
||||
|
||||
:params
|
||||
zip_file_name : path to the archive to create
|
||||
entries : map or list of archive entries
|
||||
return value : `true` when the archive is written
|
||||
|
||||
:see
|
||||
>sys
|
||||
zip_list
|
||||
zip_read
|
||||
zip_extract
|
||||
DTree
|
||||
|
||||
:content
|
||||
Creates a ZIP archive at `zip_file_name`.
|
||||
|
||||
`entries` can be a simple map where each key is the archive member name and each value is the file content:
|
||||
|
||||
```uce
|
||||
DTree entries;
|
||||
entries["hello.txt"] = "Hello ZIP";
|
||||
entries["nested/readme.txt"] = "Nested file";
|
||||
zip_create("/tmp/example.zip", entries);
|
||||
```
|
||||
|
||||
For list-shaped input or when the map key should not be the member name, each child can provide explicit fields:
|
||||
|
||||
```uce
|
||||
DTree item;
|
||||
item["name"] = "data/value.txt";
|
||||
item["content"] = "42";
|
||||
entries["ignored-key"] = item;
|
||||
```
|
||||
|
||||
An entry may also provide `file` instead of `content`; UCE reads that source file and stores its contents under `name`.
|
||||
|
||||
Entry names are normalized to forward slashes and rejected when they are absolute paths, drive-qualified paths, empty names, or contain `..` path segments.
|
||||
@@ -0,0 +1,24 @@
|
||||
:sig
|
||||
bool zip_extract(String zip_file_name, String destination_directory)
|
||||
|
||||
:params
|
||||
zip_file_name : path to the ZIP archive
|
||||
destination_directory : directory that should receive extracted files
|
||||
return value : `true` when extraction completes
|
||||
|
||||
:see
|
||||
>sys
|
||||
zip_create
|
||||
zip_list
|
||||
zip_read
|
||||
|
||||
:content
|
||||
Extracts every member in a ZIP archive into `destination_directory`.
|
||||
|
||||
```uce
|
||||
zip_extract("/tmp/example.zip", "/tmp/example-extract");
|
||||
```
|
||||
|
||||
UCE creates the destination directory and any needed child directories when they do not already exist.
|
||||
|
||||
For safety, every member name is normalized and checked before extraction. Absolute paths, drive-qualified paths, empty names, and `..` path segments are rejected so archives cannot write outside the destination directory.
|
||||
@@ -0,0 +1,38 @@
|
||||
:sig
|
||||
DTree zip_list(String zip_file_name)
|
||||
|
||||
:params
|
||||
zip_file_name : path to the ZIP archive to inspect
|
||||
return value : DTree containing archive metadata and an `entries` array
|
||||
|
||||
:see
|
||||
>sys
|
||||
zip_create
|
||||
zip_read
|
||||
zip_extract
|
||||
DTree
|
||||
|
||||
:content
|
||||
Reads the central directory from `zip_file_name` and returns structured metadata.
|
||||
|
||||
The returned tree contains:
|
||||
|
||||
- `file` : archive path
|
||||
- `count` : number of entries
|
||||
- `entries` : list of entry metadata
|
||||
|
||||
Each entry contains:
|
||||
|
||||
- `name`
|
||||
- `index`
|
||||
- `size`
|
||||
- `compressed_size`
|
||||
- `is_directory`
|
||||
- `method`
|
||||
|
||||
Example:
|
||||
|
||||
```uce
|
||||
DTree info = zip_list("/tmp/example.zip");
|
||||
print(json_encode(info));
|
||||
```
|
||||
@@ -0,0 +1,24 @@
|
||||
:sig
|
||||
String zip_read(String zip_file_name, String entry_name)
|
||||
|
||||
:params
|
||||
zip_file_name : path to the ZIP archive
|
||||
entry_name : member name to read from the archive
|
||||
return value : uncompressed entry contents
|
||||
|
||||
:see
|
||||
>sys
|
||||
zip_create
|
||||
zip_list
|
||||
zip_extract
|
||||
|
||||
:content
|
||||
Reads one file member from a ZIP archive and returns its uncompressed bytes as a `String`.
|
||||
|
||||
```uce
|
||||
String body = zip_read("/tmp/example.zip", "hello.txt");
|
||||
```
|
||||
|
||||
`entry_name` is normalized to forward slashes and rejected when it is empty, absolute, drive-qualified, or contains a `..` path segment.
|
||||
|
||||
Use `zip_list()` when you need to discover entry names before reading them.
|
||||
Reference in New Issue
Block a user