@shd101wyy/yo 0.0.31 → 0.0.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -18
- package/out/cjs/index.cjs +890 -1002
- package/out/cjs/yo-cli.cjs +954 -1069
- package/out/esm/index.mjs +786 -898
- package/out/types/src/build-runner.d.ts +3 -1
- package/out/types/src/codegen/codegen-c.d.ts +2 -0
- package/out/types/src/codegen/exprs/asm.d.ts +4 -0
- package/out/types/src/codegen/index.d.ts +10 -0
- package/out/types/src/codegen/utils/index.d.ts +1 -0
- package/out/types/src/evaluator/builtins/asm.d.ts +13 -0
- package/out/types/src/evaluator/builtins/build.d.ts +23 -10
- package/out/types/src/evaluator/exprs/while.d.ts +2 -1
- package/out/types/src/expr-traversal.d.ts +1 -0
- package/out/types/src/expr.d.ts +6 -2
- package/out/types/src/fetch.d.ts +1 -0
- package/out/types/src/module-manager.d.ts +1 -0
- package/out/types/src/pkg-config.d.ts +7 -2
- package/out/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/std/build.yo +57 -16
- package/std/{toml → encoding}/toml.yo +6 -1
- package/std/http/client.yo +359 -0
- package/std/http/index.yo +22 -0
- package/std/log/{log.yo → index.yo} +2 -2
- package/std/regex/{regex.yo → index.yo} +1 -1
- package/std/url/{url.yo → index.yo} +2 -2
- /package/std/glob/{glob.yo → index.yo} +0 -0
package/package.json
CHANGED
package/std/build.yo
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
//
|
|
7
7
|
// Usage in build.yo:
|
|
8
8
|
// build :: import "std/build";
|
|
9
|
-
// build.
|
|
9
|
+
// mod :: build.module({ name: "my-app", root: "./src/lib.yo" });
|
|
10
10
|
// exe :: build.executable({ name: "my-app", root: "./src/main.yo" });
|
|
11
11
|
// install :: build.step("install", "Build all artifacts");
|
|
12
12
|
// install.depend_on(exe);
|
|
@@ -61,12 +61,6 @@ export target_host;
|
|
|
61
61
|
// Usage: build.executable({ name: "app", root: "./src/main.yo" })
|
|
62
62
|
// Unspecified fields get their default values.
|
|
63
63
|
|
|
64
|
-
Project :: struct(
|
|
65
|
-
name : comptime_string,
|
|
66
|
-
(root : comptime_string) ?= "./src/lib.yo"
|
|
67
|
-
);
|
|
68
|
-
export Project;
|
|
69
|
-
|
|
70
64
|
Executable :: struct(
|
|
71
65
|
name : comptime_string,
|
|
72
66
|
root : comptime_string,
|
|
@@ -100,6 +94,26 @@ TestSuite :: struct(
|
|
|
100
94
|
);
|
|
101
95
|
export TestSuite;
|
|
102
96
|
|
|
97
|
+
// ── Build module type ────────────────────────────────────────────────
|
|
98
|
+
// Represents a named source module that declares system library requirements.
|
|
99
|
+
// Modules are the unit of reuse across Yo dependencies.
|
|
100
|
+
|
|
101
|
+
BuildModule :: struct(
|
|
102
|
+
name : comptime_string,
|
|
103
|
+
root : comptime_string,
|
|
104
|
+
_dep : comptime_string // dependency name, empty for local modules
|
|
105
|
+
);
|
|
106
|
+
export BuildModule;
|
|
107
|
+
|
|
108
|
+
// ── Import entry type ────────────────────────────────────────────────
|
|
109
|
+
// Pairs an import name with a module for use with add_import/add_import_list.
|
|
110
|
+
|
|
111
|
+
ImportEntry :: struct(
|
|
112
|
+
name : comptime_string,
|
|
113
|
+
module : BuildModule
|
|
114
|
+
);
|
|
115
|
+
export ImportEntry;
|
|
116
|
+
|
|
103
117
|
// ── Step type ────────────────────────────────────────────────────────
|
|
104
118
|
// Returned by all build functions. Used to wire dependencies between steps.
|
|
105
119
|
|
|
@@ -118,6 +132,17 @@ impl(Step,
|
|
|
118
132
|
}),
|
|
119
133
|
link : (fn(comptime(self) : Self, comptime(library) : Step) -> comptime(unit))({
|
|
120
134
|
__yo_build_link(self.name, library.name);
|
|
135
|
+
}),
|
|
136
|
+
add_import : (fn(comptime(self) : Self, comptime(entry) : ImportEntry) -> comptime(unit))({
|
|
137
|
+
__yo_build_add_import(self.name, entry.name, entry.module.name, entry.module._dep);
|
|
138
|
+
})
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// ── Build module methods ─────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
impl(BuildModule,
|
|
144
|
+
link : (fn(comptime(self) : Self, comptime(library) : Step) -> comptime(unit))({
|
|
145
|
+
__yo_build_module_link(self.name, library.name);
|
|
121
146
|
})
|
|
122
147
|
);
|
|
123
148
|
|
|
@@ -150,10 +175,10 @@ export PathDependency;
|
|
|
150
175
|
|
|
151
176
|
SystemLibrary :: struct(
|
|
152
177
|
name : comptime_string,
|
|
153
|
-
pkg_config : comptime_string,
|
|
154
178
|
(fallback_include : comptime_string) ?= "",
|
|
155
179
|
(fallback_lib : comptime_string) ?= "",
|
|
156
|
-
(fallback_link : comptime_string) ?= ""
|
|
180
|
+
(fallback_link : comptime_string) ?= "",
|
|
181
|
+
(defines : comptime_string) ?= ""
|
|
157
182
|
);
|
|
158
183
|
export SystemLibrary;
|
|
159
184
|
|
|
@@ -172,6 +197,12 @@ impl(Dependency,
|
|
|
172
197
|
artifact : (fn(comptime(self) : Self, comptime(artifact_name) : comptime_string) -> comptime(Step))({
|
|
173
198
|
__yo_build_dep_artifact(self.name, artifact_name);
|
|
174
199
|
Step(name: artifact_name, kind: StepKind.StaticLibrary)
|
|
200
|
+
}),
|
|
201
|
+
// Get a module from the dependency's build.yo.
|
|
202
|
+
// Empty module_name defaults to the sole module if exactly one exists.
|
|
203
|
+
module : (fn(comptime(self) : Self, comptime(module_name) : comptime_string) -> comptime(BuildModule))({
|
|
204
|
+
_encoded :: __yo_build_dep_module(self.name, module_name);
|
|
205
|
+
BuildModule(name: module_name, root: "", _dep: self.name)
|
|
175
206
|
})
|
|
176
207
|
);
|
|
177
208
|
|
|
@@ -179,12 +210,6 @@ impl(Dependency,
|
|
|
179
210
|
// All registration functions return a Step value, allowing steps to be
|
|
180
211
|
// wired together as dependencies via step.depend_on(dep).
|
|
181
212
|
|
|
182
|
-
// Register project metadata.
|
|
183
|
-
project :: (fn(comptime(config) : Project) -> comptime(unit)) {
|
|
184
|
-
__yo_build_project(config.name, config.root);
|
|
185
|
-
};
|
|
186
|
-
export project;
|
|
187
|
-
|
|
188
213
|
// Register an executable artifact. Returns a Step for dependency wiring.
|
|
189
214
|
executable :: (fn(comptime(config) : Executable) -> comptime(Step)) {
|
|
190
215
|
opt_str :: match(config.optimize,
|
|
@@ -273,11 +298,27 @@ export path_dependency;
|
|
|
273
298
|
|
|
274
299
|
// Register a system C library discovered via pkg-config. Returns a Step for linking.
|
|
275
300
|
system_library :: (fn(comptime(config) : SystemLibrary) -> comptime(Step)) {
|
|
276
|
-
__yo_build_system_library(config.name, config.
|
|
301
|
+
__yo_build_system_library(config.name, config.fallback_include, config.fallback_lib, config.fallback_link, config.defines);
|
|
277
302
|
Step(name: config.name, kind: StepKind.SystemLibrary)
|
|
278
303
|
};
|
|
279
304
|
export system_library;
|
|
280
305
|
|
|
306
|
+
// ── Module config type ───────────────────────────────────────────────
|
|
307
|
+
|
|
308
|
+
ModuleConfig :: struct(
|
|
309
|
+
name : comptime_string,
|
|
310
|
+
root : comptime_string
|
|
311
|
+
);
|
|
312
|
+
export ModuleConfig;
|
|
313
|
+
|
|
314
|
+
// Register a named module. Returns a BuildModule for linking system libraries
|
|
315
|
+
// and importing into executables/libraries.
|
|
316
|
+
_module :: (fn(comptime(config) : ModuleConfig) -> comptime(BuildModule)) {
|
|
317
|
+
__yo_build_module(config.name, config.root);
|
|
318
|
+
BuildModule(name: config.name, root: config.root, _dep: "")
|
|
319
|
+
};
|
|
320
|
+
export module : _module;
|
|
321
|
+
|
|
281
322
|
// Declare a user-configurable build option.
|
|
282
323
|
// Returns the option value (from CLI -Dname=value, or the default).
|
|
283
324
|
// Usage: strip :: build.option({ name: "strip", description: "Strip debug symbols", default: "false" });
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
// std/
|
|
1
|
+
// std/encoding/toml.yo - Basic TOML parser
|
|
2
2
|
//
|
|
3
3
|
// Parses a subset of TOML into a TomlValue tree.
|
|
4
4
|
// Supports: strings, integers, booleans, table sections, comments.
|
|
5
|
+
//
|
|
6
|
+
// Example:
|
|
7
|
+
// { toml_parse, TomlValue } :: import "std/encoding/toml";
|
|
8
|
+
// // Or via the encoding index:
|
|
9
|
+
// { toml_parse, TomlValue } :: import "std/encoding";
|
|
5
10
|
|
|
6
11
|
open import "../string";
|
|
7
12
|
{ ArrayList } :: import "../collections/array_list";
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
// std/http/client.yo - Async HTTP client using TCP
|
|
2
|
+
//
|
|
3
|
+
// Provides an async HTTP client and a high-level `fetch` function
|
|
4
|
+
// similar to JavaScript's fetch API.
|
|
5
|
+
//
|
|
6
|
+
// Example:
|
|
7
|
+
// { fetch, HttpResponse } :: import "std/http";
|
|
8
|
+
// { Exception } :: import "std/error";
|
|
9
|
+
//
|
|
10
|
+
// main :: (fn(using(io : IO)) -> unit)({
|
|
11
|
+
// given(exn) := Exception(throw : ((err) -> {
|
|
12
|
+
// println(err.message());
|
|
13
|
+
// escape ();
|
|
14
|
+
// }));
|
|
15
|
+
// resp := io.await(fetch(`http://example.com/api/data`, using(io)));
|
|
16
|
+
// println(resp.body);
|
|
17
|
+
// });
|
|
18
|
+
|
|
19
|
+
open import "../string";
|
|
20
|
+
{ ArrayList } :: import "../collections/array_list";
|
|
21
|
+
{ GlobalAllocator } :: import "../allocator";
|
|
22
|
+
{ malloc, free } :: GlobalAllocator;
|
|
23
|
+
open import "../fmt";
|
|
24
|
+
{ Error, AnyError, Exception } :: import "../error";
|
|
25
|
+
{ TcpStream } :: import "../net/tcp";
|
|
26
|
+
{ IpAddr, SocketAddr } :: import "../net/addr";
|
|
27
|
+
{ lookup_host } :: import "../net/dns";
|
|
28
|
+
{ Url, UrlError } :: import "../url";
|
|
29
|
+
{
|
|
30
|
+
HttpMethod, HttpHeader, HttpRequest, HttpResponse,
|
|
31
|
+
parse_response, http_status_text
|
|
32
|
+
} :: import "./http.yo";
|
|
33
|
+
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// HttpError
|
|
36
|
+
// ============================================================================
|
|
37
|
+
|
|
38
|
+
HttpError :: enum(
|
|
39
|
+
ConnectionFailed(msg: String),
|
|
40
|
+
InvalidUrl(msg: String),
|
|
41
|
+
Timeout,
|
|
42
|
+
TooManyRedirects,
|
|
43
|
+
UnsupportedScheme(scheme: String),
|
|
44
|
+
ResponseTooLarge,
|
|
45
|
+
Other(msg: String)
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
impl(HttpError, ToString(
|
|
49
|
+
to_string : (self ->
|
|
50
|
+
match(self,
|
|
51
|
+
.ConnectionFailed(msg) => `HTTP connection failed: ${msg}`,
|
|
52
|
+
.InvalidUrl(msg) => `Invalid URL: ${msg}`,
|
|
53
|
+
.Timeout => `HTTP request timed out`,
|
|
54
|
+
.TooManyRedirects => `Too many redirects`,
|
|
55
|
+
.UnsupportedScheme(scheme) => `Unsupported scheme: ${scheme}`,
|
|
56
|
+
.ResponseTooLarge => `Response too large`,
|
|
57
|
+
.Other(msg) => `HTTP error: ${msg}`
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
));
|
|
61
|
+
|
|
62
|
+
impl(HttpError, Error());
|
|
63
|
+
|
|
64
|
+
export HttpError;
|
|
65
|
+
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// FetchOptions
|
|
68
|
+
// ============================================================================
|
|
69
|
+
|
|
70
|
+
FetchOptions :: object(
|
|
71
|
+
method : HttpMethod,
|
|
72
|
+
headers : ArrayList(HttpHeader),
|
|
73
|
+
body : String
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
impl(FetchOptions,
|
|
77
|
+
new : (fn() -> Self)(
|
|
78
|
+
Self(
|
|
79
|
+
method: .GET,
|
|
80
|
+
headers: ArrayList(HttpHeader).new(),
|
|
81
|
+
body: ``
|
|
82
|
+
)
|
|
83
|
+
),
|
|
84
|
+
|
|
85
|
+
with_method : (fn(self: Self, method: HttpMethod) -> Self)({
|
|
86
|
+
self.method = method;
|
|
87
|
+
self
|
|
88
|
+
}),
|
|
89
|
+
|
|
90
|
+
with_header : (fn(self: Self, name: String, value: String) -> Self)({
|
|
91
|
+
self.headers.push(HttpHeader.new(name, value));
|
|
92
|
+
self
|
|
93
|
+
}),
|
|
94
|
+
|
|
95
|
+
with_body : (fn(self: Self, body: String) -> Self)({
|
|
96
|
+
self.body = body;
|
|
97
|
+
self
|
|
98
|
+
})
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
export FetchOptions;
|
|
102
|
+
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// Internal helpers
|
|
105
|
+
// ============================================================================
|
|
106
|
+
|
|
107
|
+
// Find the position of \r\n\r\n (end of HTTP headers)
|
|
108
|
+
_find_header_end :: (fn(data: ArrayList(u8)) -> usize)({
|
|
109
|
+
len := data.len();
|
|
110
|
+
cond(
|
|
111
|
+
(len < usize(4)) => { return usize(0); },
|
|
112
|
+
true => ()
|
|
113
|
+
);
|
|
114
|
+
i := usize(0);
|
|
115
|
+
limit := (len - usize(3));
|
|
116
|
+
while runtime((i < limit)), {
|
|
117
|
+
cond(
|
|
118
|
+
((data.get(i).unwrap() == u8(13)) &&
|
|
119
|
+
((data.get((i + usize(1))).unwrap() == u8(10)) &&
|
|
120
|
+
((data.get((i + usize(2))).unwrap() == u8(13)) &&
|
|
121
|
+
(data.get((i + usize(3))).unwrap() == u8(10))))) => {
|
|
122
|
+
return i;
|
|
123
|
+
},
|
|
124
|
+
true => ()
|
|
125
|
+
);
|
|
126
|
+
i = (i + usize(1));
|
|
127
|
+
};
|
|
128
|
+
usize(0)
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Parse Content-Length from headers; returns -1 if not found.
|
|
132
|
+
_find_content_length :: (fn(data: ArrayList(u8), header_end: usize) -> i32)({
|
|
133
|
+
// Build header string up to header_end
|
|
134
|
+
header_bytes := ArrayList(u8).new();
|
|
135
|
+
i := usize(0);
|
|
136
|
+
while runtime((i < header_end)), {
|
|
137
|
+
header_bytes.push(data.get(i).unwrap());
|
|
138
|
+
i = (i + usize(1));
|
|
139
|
+
};
|
|
140
|
+
header_str := String.from_bytes(header_bytes).to_lowercase();
|
|
141
|
+
match(header_str.index_of(`content-length:`),
|
|
142
|
+
.Some(pos) => {
|
|
143
|
+
val_start := (pos + usize(16));
|
|
144
|
+
// Find end of value by looking for \r\n after val_start
|
|
145
|
+
rest := header_str.substring(val_start, header_str.len());
|
|
146
|
+
match(rest.index_of(`\r\n`),
|
|
147
|
+
.Some(eol) => {
|
|
148
|
+
val_str := rest.substring(usize(0), eol).trim();
|
|
149
|
+
match(val_str.parse_i32(),
|
|
150
|
+
.Some(n) => n,
|
|
151
|
+
.None => i32(-1)
|
|
152
|
+
)
|
|
153
|
+
},
|
|
154
|
+
.None => {
|
|
155
|
+
val_str := rest.trim();
|
|
156
|
+
match(val_str.parse_i32(),
|
|
157
|
+
.Some(n) => n,
|
|
158
|
+
.None => i32(-1)
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
)
|
|
162
|
+
},
|
|
163
|
+
.None => i32(-1)
|
|
164
|
+
)
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// ============================================================================
|
|
168
|
+
// Internal: read full HTTP response from a TCP stream
|
|
169
|
+
// ============================================================================
|
|
170
|
+
|
|
171
|
+
_read_http_response :: (fn(stream: TcpStream, using(io : IO, exn : Exception)) -> String)({
|
|
172
|
+
buf_size := usize(8192);
|
|
173
|
+
buf := *(u8)(malloc(buf_size).unwrap());
|
|
174
|
+
result := ArrayList(u8).new();
|
|
175
|
+
|
|
176
|
+
// Read until connection closes or we've read the full response
|
|
177
|
+
done := false;
|
|
178
|
+
while runtime(!(done)), {
|
|
179
|
+
n := io.await(stream.read(buf, buf_size, using(io)));
|
|
180
|
+
cond(
|
|
181
|
+
(n <= i32(0)) => {
|
|
182
|
+
done = true;
|
|
183
|
+
},
|
|
184
|
+
true => {
|
|
185
|
+
i := usize(0);
|
|
186
|
+
while runtime((i < usize(n))), {
|
|
187
|
+
result.push((buf &+ i).*);
|
|
188
|
+
i = (i + usize(1));
|
|
189
|
+
};
|
|
190
|
+
// Check if we've received the full response by looking for
|
|
191
|
+
// Content-Length or end of chunked transfer
|
|
192
|
+
// For simplicity, check if the header section is complete
|
|
193
|
+
// and if Content-Length is satisfied
|
|
194
|
+
cond(
|
|
195
|
+
(result.len() > usize(4)) => {
|
|
196
|
+
// Check if we have a complete header section (\r\n\r\n)
|
|
197
|
+
header_end := _find_header_end(result);
|
|
198
|
+
cond(
|
|
199
|
+
(header_end > usize(0)) => {
|
|
200
|
+
// Check Content-Length
|
|
201
|
+
cl := _find_content_length(result, header_end);
|
|
202
|
+
cond(
|
|
203
|
+
(cl >= i32(0)) => {
|
|
204
|
+
body_start := (header_end + usize(4));
|
|
205
|
+
body_len := (result.len() - body_start);
|
|
206
|
+
cond(
|
|
207
|
+
(body_len >= usize(cl)) => {
|
|
208
|
+
done = true;
|
|
209
|
+
},
|
|
210
|
+
true => ()
|
|
211
|
+
);
|
|
212
|
+
},
|
|
213
|
+
true => ()
|
|
214
|
+
);
|
|
215
|
+
},
|
|
216
|
+
true => ()
|
|
217
|
+
);
|
|
218
|
+
},
|
|
219
|
+
true => ()
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
);
|
|
223
|
+
};
|
|
224
|
+
free(.Some(*(void)(buf)));
|
|
225
|
+
String.from_bytes(result)
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// ============================================================================
|
|
229
|
+
// fetch — High-level async HTTP request (like JavaScript's fetch)
|
|
230
|
+
// ============================================================================
|
|
231
|
+
|
|
232
|
+
// Perform an HTTP request with custom options.
|
|
233
|
+
//
|
|
234
|
+
// Example:
|
|
235
|
+
// opts := FetchOptions.new().with_method(.POST).with_body(`{"key": "value"}`);
|
|
236
|
+
// resp := io.await(fetch_with(`http://example.com/api`, opts, using(io)));
|
|
237
|
+
fetch_with :: (fn(url_str: String, opts: FetchOptions, using(io : IO)) ->
|
|
238
|
+
Impl(Future(HttpResponse, IO, Exception))
|
|
239
|
+
)(
|
|
240
|
+
io.async((using(io : IO, exn : Exception)) => {
|
|
241
|
+
// Parse URL
|
|
242
|
+
url := Url.parse(url_str.as_str(), using(exn));
|
|
243
|
+
|
|
244
|
+
// Validate scheme
|
|
245
|
+
scheme := url.scheme();
|
|
246
|
+
is_http := (scheme == `http`);
|
|
247
|
+
is_https := (scheme == `https`);
|
|
248
|
+
cond(
|
|
249
|
+
(is_http || is_https) => (),
|
|
250
|
+
true => {
|
|
251
|
+
exn.throw(dyn HttpError.UnsupportedScheme(scheme));
|
|
252
|
+
}
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
// Extract host
|
|
256
|
+
host := match(url.host(),
|
|
257
|
+
.Some(h) => h,
|
|
258
|
+
.None => {
|
|
259
|
+
exn.throw(dyn HttpError.InvalidUrl(`missing host`));
|
|
260
|
+
`_` // unreachable
|
|
261
|
+
}
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
// Determine port (default: 80 for http, 443 for https)
|
|
265
|
+
port := match(url.port(),
|
|
266
|
+
.Some(p) => p,
|
|
267
|
+
.None => cond(
|
|
268
|
+
(scheme == `https`) => u16(443),
|
|
269
|
+
true => u16(80)
|
|
270
|
+
)
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
// Resolve hostname to IP
|
|
274
|
+
addrs := io.await(lookup_host(host, using(io)));
|
|
275
|
+
cond(
|
|
276
|
+
(addrs.len() == usize(0)) => {
|
|
277
|
+
exn.throw(dyn HttpError.ConnectionFailed(`DNS resolution failed for ${host}`));
|
|
278
|
+
},
|
|
279
|
+
true => ()
|
|
280
|
+
);
|
|
281
|
+
ip := addrs.get(usize(0)).unwrap();
|
|
282
|
+
addr := SocketAddr.new(ip, port);
|
|
283
|
+
|
|
284
|
+
// Connect
|
|
285
|
+
stream := io.await(TcpStream.connect(addr, using(io)));
|
|
286
|
+
|
|
287
|
+
// Build the request path (include query string if present)
|
|
288
|
+
req_path := url.path();
|
|
289
|
+
cond(
|
|
290
|
+
req_path.is_empty() => { req_path = `/`; },
|
|
291
|
+
true => ()
|
|
292
|
+
);
|
|
293
|
+
match(url.query(),
|
|
294
|
+
.Some(q) => { req_path = `${req_path}?${q}`; },
|
|
295
|
+
.None => ()
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
// Build HTTP request
|
|
299
|
+
req := HttpRequest.new(opts.method, req_path);
|
|
300
|
+
req.set_host(host);
|
|
301
|
+
|
|
302
|
+
// Add user-provided headers
|
|
303
|
+
hi := usize(0);
|
|
304
|
+
while runtime((hi < opts.headers.len())), {
|
|
305
|
+
h := opts.headers.get(hi).unwrap();
|
|
306
|
+
req.set_header(h.name, h.value);
|
|
307
|
+
hi = (hi + usize(1));
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
// Set Content-Length if body is provided
|
|
311
|
+
cond(
|
|
312
|
+
(!(opts.body.is_empty())) => {
|
|
313
|
+
req.set_body(opts.body);
|
|
314
|
+
req.set_header(`Content-Length`, `${opts.body.len()}`);
|
|
315
|
+
},
|
|
316
|
+
true => ()
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
// Set Connection: close to ensure the server closes after response
|
|
320
|
+
req.set_header(`Connection`, `close`);
|
|
321
|
+
|
|
322
|
+
// Send request
|
|
323
|
+
req_str := req.to_string();
|
|
324
|
+
io.await(stream.write_string(req_str, using(io)));
|
|
325
|
+
|
|
326
|
+
// Read response
|
|
327
|
+
raw_response := _read_http_response(stream, using(io, exn));
|
|
328
|
+
|
|
329
|
+
// Close connection
|
|
330
|
+
io.await(stream.close(using(io)));
|
|
331
|
+
|
|
332
|
+
// Parse response
|
|
333
|
+
match(parse_response(raw_response),
|
|
334
|
+
.Ok(resp) => resp,
|
|
335
|
+
.Err(e) => {
|
|
336
|
+
exn.throw(dyn HttpError.Other(e));
|
|
337
|
+
HttpResponse.new(i32(0), ``) // unreachable
|
|
338
|
+
}
|
|
339
|
+
)
|
|
340
|
+
})
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
export fetch_with;
|
|
344
|
+
|
|
345
|
+
// Perform an HTTP GET request to the given URL string.
|
|
346
|
+
// Returns the HttpResponse on success.
|
|
347
|
+
//
|
|
348
|
+
// Example:
|
|
349
|
+
// resp := io.await(fetch(`http://example.com`, using(io)));
|
|
350
|
+
// cond(resp.is_ok() => println(resp.body), true => println(`Error`));
|
|
351
|
+
fetch :: (fn(url_str: String, using(io : IO)) ->
|
|
352
|
+
Impl(Future(HttpResponse, IO, Exception))
|
|
353
|
+
)(
|
|
354
|
+
io.async((using(io : IO, exn : Exception)) => {
|
|
355
|
+
return io.await(fetch_with(url_str, FetchOptions.new(), using(io)));
|
|
356
|
+
})
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
export fetch;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// std/http - HTTP types, request/response builders, and async client.
|
|
2
|
+
//
|
|
3
|
+
// Example:
|
|
4
|
+
// { HttpRequest, HttpResponse, fetch } :: import "std/http";
|
|
5
|
+
// { Exception } :: import "std/error";
|
|
6
|
+
//
|
|
7
|
+
// main :: (fn(using(io : IO)) -> unit)({
|
|
8
|
+
// given(exn) := Exception(throw : ((err) -> {
|
|
9
|
+
// println(err.message());
|
|
10
|
+
// escape ();
|
|
11
|
+
// }));
|
|
12
|
+
// resp := io.await(fetch(`http://example.com`, using(io)));
|
|
13
|
+
// println(resp.body);
|
|
14
|
+
// });
|
|
15
|
+
|
|
16
|
+
_http :: import "./http.yo";
|
|
17
|
+
_client :: import "./client.yo";
|
|
18
|
+
|
|
19
|
+
export
|
|
20
|
+
...(_http),
|
|
21
|
+
...(_client)
|
|
22
|
+
;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// std/log
|
|
1
|
+
// std/log - Structured logging
|
|
2
2
|
//
|
|
3
3
|
// Provides a global logger with configurable level and output destination.
|
|
4
4
|
//
|
|
5
5
|
// Example:
|
|
6
|
-
// { info, warn, error, set_level, Level } :: import "std/log
|
|
6
|
+
// { info, warn, error, set_level, Level } :: import "std/log";
|
|
7
7
|
//
|
|
8
8
|
// set_level(.Info);
|
|
9
9
|
// info(String.from("application started"));
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// std/url
|
|
1
|
+
// std/url - URL parsing and formatting
|
|
2
2
|
//
|
|
3
3
|
// Parse, manipulate, and format URLs per RFC 3986 (simplified).
|
|
4
4
|
//
|
|
5
5
|
// Example:
|
|
6
|
-
// { Url, UrlError } :: import "std/url
|
|
6
|
+
// { Url, UrlError } :: import "std/url";
|
|
7
7
|
//
|
|
8
8
|
// url := Url.parse("https://example.com:8080/path?q=1#frag").unwrap();
|
|
9
9
|
// assert(url.scheme() == `https`);
|
|
File without changes
|