@mikrojs/firmware 0.0.7
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/LICENSE +21 -0
- package/README.md +30 -0
- package/bin/idf.py +7 -0
- package/chips.json +3 -0
- package/cmake.js +9 -0
- package/components/mikrojs/CMakeLists.txt +187 -0
- package/components/mikrojs/Kconfig +55 -0
- package/components/mikrojs/idf_component.yml +6 -0
- package/components/mikrojs/include/mem.h +3 -0
- package/components/mikrojs/include/mik_color.h +3 -0
- package/components/mikrojs/include/mik_http_internal.h +77 -0
- package/components/mikrojs/include/mikrojs.h +5 -0
- package/components/mikrojs/include/mikrojs_esp32.h +65 -0
- package/components/mikrojs/include/private.h +10 -0
- package/components/mikrojs/include/utils.h +3 -0
- package/components/mikrojs/mik_ble.cpp +1588 -0
- package/components/mikrojs/mik_ble_c_shim.c +61 -0
- package/components/mikrojs/mik_ble_c_shim.h +37 -0
- package/components/mikrojs/mik_config.cpp +167 -0
- package/components/mikrojs/mik_deploy.cpp +584 -0
- package/components/mikrojs/mik_http.cpp +916 -0
- package/components/mikrojs/mik_i2c.cpp +364 -0
- package/components/mikrojs/mik_main.cpp +542 -0
- package/components/mikrojs/mik_neopixel.cpp +437 -0
- package/components/mikrojs/mik_nvs_kv.cpp +219 -0
- package/components/mikrojs/mik_pin.cpp +195 -0
- package/components/mikrojs/mik_pwm.cpp +525 -0
- package/components/mikrojs/mik_recovery.cpp +86 -0
- package/components/mikrojs/mik_rtc.cpp +305 -0
- package/components/mikrojs/mik_serial_io.cpp +362 -0
- package/components/mikrojs/mik_sleep.cpp +226 -0
- package/components/mikrojs/mik_sntp.cpp +275 -0
- package/components/mikrojs/mik_spi.cpp +330 -0
- package/components/mikrojs/mik_uart.cpp +497 -0
- package/components/mikrojs/mik_wifi.cpp +1434 -0
- package/components/mikrojs/platform_esp32.cpp +192 -0
- package/components/mikrojs/test/CMakeLists.txt +32 -0
- package/components/mikrojs/test/abort_test.cpp +254 -0
- package/components/mikrojs/test/ble_test.cpp +714 -0
- package/components/mikrojs/test/fs_js_test.cpp +458 -0
- package/components/mikrojs/test/fs_pub_test.cpp +312 -0
- package/components/mikrojs/test/http_test.cpp +475 -0
- package/components/mikrojs/test/i2c_test.cpp +138 -0
- package/components/mikrojs/test/modules_extended_test.cpp +137 -0
- package/components/mikrojs/test/modules_test.cpp +131 -0
- package/components/mikrojs/test/pins_test.cpp +47 -0
- package/components/mikrojs/test/pwm_test.cpp +166 -0
- package/components/mikrojs/test/repl_protocol_test.cpp +405 -0
- package/components/mikrojs/test/rtc_test.cpp +331 -0
- package/components/mikrojs/test/runtime_test.cpp +89 -0
- package/components/mikrojs/test/sleep_test.cpp +222 -0
- package/components/mikrojs/test/sntp_test.cpp +249 -0
- package/components/mikrojs/test/stdio_test.cpp +449 -0
- package/components/mikrojs/test/sys_test.cpp +165 -0
- package/components/mikrojs/test/text_encoding_test.cpp +224 -0
- package/components/mikrojs/test/timers_js_test.cpp +244 -0
- package/components/mikrojs/test/timers_test.cpp +79 -0
- package/components/mikrojs/test/wifi_test.cpp +599 -0
- package/default-app/main/CMakeLists.txt +3 -0
- package/default-app/main/main.cpp +5 -0
- package/discover.js +77 -0
- package/index.d.ts +7 -0
- package/index.js +20 -0
- package/package.json +61 -0
- package/partitions.csv +5 -0
- package/prebuilds/esp32/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32/flasher_args.json +24 -0
- package/prebuilds/esp32/mikrojs.bin +0 -0
- package/prebuilds/esp32/partition_table/partition-table.bin +0 -0
- package/prebuilds/esp32c3/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32c3/flasher_args.json +24 -0
- package/prebuilds/esp32c3/mikrojs.bin +0 -0
- package/prebuilds/esp32c3/partition_table/partition-table.bin +0 -0
- package/prebuilds/esp32c6/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32c6/flasher_args.json +24 -0
- package/prebuilds/esp32c6/mikrojs.bin +0 -0
- package/prebuilds/esp32c6/partition_table/partition-table.bin +0 -0
- package/prebuilds/esp32s3/bootloader/bootloader.bin +0 -0
- package/prebuilds/esp32s3/flasher_args.json +24 -0
- package/prebuilds/esp32s3/mikrojs.bin +0 -0
- package/prebuilds/esp32s3/partition_table/partition-table.bin +0 -0
- package/project.cmake +101 -0
- package/resolve.js +54 -0
- package/sdkconfig.defaults +127 -0
- package/sdkconfig.defaults.esp32 +8 -0
- package/sdkconfig.defaults.esp32c3 +15 -0
- package/sdkconfig.defaults.esp32c6 +26 -0
- package/sdkconfig.defaults.esp32s3 +22 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
#include "mikrojs.h"
|
|
2
|
+
#include "private.h"
|
|
3
|
+
#include "quickjs.h"
|
|
4
|
+
#include "unity.h"
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* Tests for the public mikrojs/fs module (throwing API).
|
|
8
|
+
* Requires LittleFS mounted at /littlefs with a dummy.txt containing "Hello World".
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
static MIKRuntime* mik_rt;
|
|
12
|
+
static JSContext* mik_ctx;
|
|
13
|
+
|
|
14
|
+
/* LittleFS is mounted/unmounted by the global setUp/tearDown in
|
|
15
|
+
* fs_js_test.cpp (shared across all [fs] tests). */
|
|
16
|
+
|
|
17
|
+
static void pub_fs_setup() {
|
|
18
|
+
mik_rt = MIK_NewRuntime();
|
|
19
|
+
MIK_SetFSBasePath(mik_rt, "/littlefs");
|
|
20
|
+
mik_ctx = MIK_GetJSContext(mik_rt);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static void pub_fs_teardown() {
|
|
24
|
+
MIK_FreeRuntime(mik_rt);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static JSValue pub_fs_eval(const char* code) {
|
|
28
|
+
JSValue ret = MIK_EvalModuleContent(mik_ctx, "mikrojs/test", code, strlen(code));
|
|
29
|
+
if (!JS_IsException(ret)) {
|
|
30
|
+
JS_FreeValue(mik_ctx, ret);
|
|
31
|
+
mik__execute_jobs(mik_ctx);
|
|
32
|
+
}
|
|
33
|
+
return ret;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static bool get_global_bool(const char* name) {
|
|
37
|
+
JSValue global = JS_GetGlobalObject(mik_ctx);
|
|
38
|
+
JSValue val = JS_GetPropertyStr(mik_ctx, global, name);
|
|
39
|
+
bool result = JS_ToBool(mik_ctx, val);
|
|
40
|
+
JS_FreeValue(mik_ctx, val);
|
|
41
|
+
JS_FreeValue(mik_ctx, global);
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static char* get_global_string(const char* name) {
|
|
46
|
+
JSValue global = JS_GetGlobalObject(mik_ctx);
|
|
47
|
+
JSValue val = JS_GetPropertyStr(mik_ctx, global, name);
|
|
48
|
+
const char* str = JS_ToCString(mik_ctx, val);
|
|
49
|
+
char* result = str ? strdup(str) : NULL;
|
|
50
|
+
if (str) JS_FreeCString(mik_ctx, str);
|
|
51
|
+
JS_FreeValue(mik_ctx, val);
|
|
52
|
+
JS_FreeValue(mik_ctx, global);
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static int32_t get_global_int32(const char* name) {
|
|
57
|
+
JSValue global = JS_GetGlobalObject(mik_ctx);
|
|
58
|
+
JSValue val = JS_GetPropertyStr(mik_ctx, global, name);
|
|
59
|
+
int32_t result;
|
|
60
|
+
JS_ToInt32(mik_ctx, &result, val);
|
|
61
|
+
JS_FreeValue(mik_ctx, val);
|
|
62
|
+
JS_FreeValue(mik_ctx, global);
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* ── readFile ────────────────────────────────────────────────────── */
|
|
67
|
+
|
|
68
|
+
TEST_CASE("mikrojs/fs readFile returns Uint8Array", "[fs]") {
|
|
69
|
+
pub_fs_setup();
|
|
70
|
+
|
|
71
|
+
JSValue ret = pub_fs_eval(R"(
|
|
72
|
+
import { readFile } from "mikrojs/fs";
|
|
73
|
+
const r = readFile("/dummy.txt");
|
|
74
|
+
globalThis.__ok = r.ok;
|
|
75
|
+
if (r.ok) globalThis.__content = new TextDecoder().decode(r.value);
|
|
76
|
+
)");
|
|
77
|
+
bool was_exception = JS_IsException(ret);
|
|
78
|
+
bool ok = get_global_bool("__ok");
|
|
79
|
+
char* content = get_global_string("__content");
|
|
80
|
+
pub_fs_teardown();
|
|
81
|
+
|
|
82
|
+
TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
|
|
83
|
+
TEST_ASSERT_TRUE(ok);
|
|
84
|
+
TEST_ASSERT_EQUAL_STRING("Hello World", content);
|
|
85
|
+
free(content);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
TEST_CASE("mikrojs/fs readFile with utf-8 returns string", "[fs]") {
|
|
89
|
+
pub_fs_setup();
|
|
90
|
+
|
|
91
|
+
JSValue ret = pub_fs_eval(R"(
|
|
92
|
+
import { readFile } from "mikrojs/fs";
|
|
93
|
+
const r = readFile("/dummy.txt", "utf-8");
|
|
94
|
+
globalThis.__ok = r.ok;
|
|
95
|
+
globalThis.__isString = r.ok && typeof r.value === "string";
|
|
96
|
+
if (r.ok) globalThis.__content = r.value;
|
|
97
|
+
)");
|
|
98
|
+
bool was_exception = JS_IsException(ret);
|
|
99
|
+
bool ok = get_global_bool("__ok");
|
|
100
|
+
bool is_string = get_global_bool("__isString");
|
|
101
|
+
char* content = get_global_string("__content");
|
|
102
|
+
pub_fs_teardown();
|
|
103
|
+
|
|
104
|
+
TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
|
|
105
|
+
TEST_ASSERT_TRUE(ok);
|
|
106
|
+
TEST_ASSERT_TRUE_MESSAGE(is_string, "readFile with utf-8 should return string");
|
|
107
|
+
TEST_ASSERT_EQUAL_STRING("Hello World", content);
|
|
108
|
+
free(content);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
TEST_CASE("mikrojs/fs readFile returns NotFound error for missing file", "[fs]") {
|
|
112
|
+
pub_fs_setup();
|
|
113
|
+
|
|
114
|
+
JSValue ret = pub_fs_eval(R"(
|
|
115
|
+
import { readFile } from "mikrojs/fs";
|
|
116
|
+
const r = readFile("/nonexistent.txt");
|
|
117
|
+
globalThis.__ok = r.ok;
|
|
118
|
+
if (!r.ok) {
|
|
119
|
+
globalThis.__name = r.error.name;
|
|
120
|
+
globalThis.__path = r.error.path;
|
|
121
|
+
}
|
|
122
|
+
)");
|
|
123
|
+
bool was_exception = JS_IsException(ret);
|
|
124
|
+
bool ok = get_global_bool("__ok");
|
|
125
|
+
char* name = get_global_string("__name");
|
|
126
|
+
char* path = get_global_string("__path");
|
|
127
|
+
pub_fs_teardown();
|
|
128
|
+
|
|
129
|
+
TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
|
|
130
|
+
TEST_ASSERT_FALSE_MESSAGE(ok, "readFile should fail for missing file");
|
|
131
|
+
TEST_ASSERT_EQUAL_STRING("NotFound", name);
|
|
132
|
+
TEST_ASSERT_EQUAL_STRING("/nonexistent.txt", path);
|
|
133
|
+
free(name);
|
|
134
|
+
free(path);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* ── writeFile ───────────────────────────────────────────────────── */
|
|
138
|
+
|
|
139
|
+
TEST_CASE("mikrojs/fs writeFile and readFile roundtrip", "[fs]") {
|
|
140
|
+
pub_fs_setup();
|
|
141
|
+
|
|
142
|
+
JSValue ret = pub_fs_eval(R"(
|
|
143
|
+
import { writeFile, readFile } from "mikrojs/fs";
|
|
144
|
+
const w = writeFile("/test_pub_write.txt", "hello from pub fs");
|
|
145
|
+
const r = readFile("/test_pub_write.txt", "utf-8");
|
|
146
|
+
globalThis.__ok = w.ok && r.ok;
|
|
147
|
+
if (r.ok) globalThis.__content = r.value;
|
|
148
|
+
)");
|
|
149
|
+
bool was_exception = JS_IsException(ret);
|
|
150
|
+
bool ok = get_global_bool("__ok");
|
|
151
|
+
char* content = get_global_string("__content");
|
|
152
|
+
pub_fs_teardown();
|
|
153
|
+
|
|
154
|
+
TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
|
|
155
|
+
TEST_ASSERT_TRUE(ok);
|
|
156
|
+
TEST_ASSERT_EQUAL_STRING("hello from pub fs", content);
|
|
157
|
+
free(content);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
TEST_CASE("mikrojs/fs writeFile with Uint8Array", "[fs]") {
|
|
161
|
+
pub_fs_setup();
|
|
162
|
+
|
|
163
|
+
JSValue ret = pub_fs_eval(R"(
|
|
164
|
+
import { writeFile, readFile } from "mikrojs/fs";
|
|
165
|
+
const data = new TextEncoder().encode("binary data");
|
|
166
|
+
const w = writeFile("/test_pub_binary.txt", data);
|
|
167
|
+
const r = readFile("/test_pub_binary.txt", "utf-8");
|
|
168
|
+
globalThis.__ok = w.ok && r.ok;
|
|
169
|
+
if (r.ok) globalThis.__content = r.value;
|
|
170
|
+
)");
|
|
171
|
+
bool was_exception = JS_IsException(ret);
|
|
172
|
+
bool ok = get_global_bool("__ok");
|
|
173
|
+
char* content = get_global_string("__content");
|
|
174
|
+
pub_fs_teardown();
|
|
175
|
+
|
|
176
|
+
TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
|
|
177
|
+
TEST_ASSERT_TRUE(ok);
|
|
178
|
+
TEST_ASSERT_EQUAL_STRING("binary data", content);
|
|
179
|
+
free(content);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* ── stat ────────────────────────────────────────────────────────── */
|
|
183
|
+
|
|
184
|
+
TEST_CASE("mikrojs/fs stat returns file info", "[fs]") {
|
|
185
|
+
pub_fs_setup();
|
|
186
|
+
|
|
187
|
+
JSValue ret = pub_fs_eval(R"(
|
|
188
|
+
import { stat } from "mikrojs/fs";
|
|
189
|
+
const r = stat("/dummy.txt");
|
|
190
|
+
globalThis.__ok = r.ok;
|
|
191
|
+
if (r.ok) {
|
|
192
|
+
globalThis.__size = r.value.size;
|
|
193
|
+
globalThis.__isFile = r.value.isFile;
|
|
194
|
+
globalThis.__isDir = r.value.isDirectory;
|
|
195
|
+
}
|
|
196
|
+
)");
|
|
197
|
+
bool was_exception = JS_IsException(ret);
|
|
198
|
+
bool ok = get_global_bool("__ok");
|
|
199
|
+
int32_t size = get_global_int32("__size");
|
|
200
|
+
bool is_file = get_global_bool("__isFile");
|
|
201
|
+
bool is_dir = get_global_bool("__isDir");
|
|
202
|
+
pub_fs_teardown();
|
|
203
|
+
|
|
204
|
+
TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
|
|
205
|
+
TEST_ASSERT_TRUE(ok);
|
|
206
|
+
TEST_ASSERT_EQUAL_INT(11, size);
|
|
207
|
+
TEST_ASSERT_TRUE(is_file);
|
|
208
|
+
TEST_ASSERT_FALSE(is_dir);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
TEST_CASE("mikrojs/fs stat returns NotFound for missing path", "[fs]") {
|
|
212
|
+
pub_fs_setup();
|
|
213
|
+
|
|
214
|
+
JSValue ret = pub_fs_eval(R"(
|
|
215
|
+
import { stat } from "mikrojs/fs";
|
|
216
|
+
const r = stat("/nonexistent.txt");
|
|
217
|
+
globalThis.__ok = r.ok;
|
|
218
|
+
if (!r.ok) globalThis.__name = r.error.name;
|
|
219
|
+
)");
|
|
220
|
+
bool was_exception = JS_IsException(ret);
|
|
221
|
+
bool ok = get_global_bool("__ok");
|
|
222
|
+
char* name = get_global_string("__name");
|
|
223
|
+
pub_fs_teardown();
|
|
224
|
+
|
|
225
|
+
TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
|
|
226
|
+
TEST_ASSERT_FALSE_MESSAGE(ok, "stat should fail for missing path");
|
|
227
|
+
TEST_ASSERT_EQUAL_STRING("NotFound", name);
|
|
228
|
+
free(name);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* ── exists ──────────────────────────────────────────────────────── */
|
|
232
|
+
|
|
233
|
+
TEST_CASE("mikrojs/fs exists returns true for existing file", "[fs]") {
|
|
234
|
+
pub_fs_setup();
|
|
235
|
+
|
|
236
|
+
JSValue ret = pub_fs_eval(R"(
|
|
237
|
+
import { exists } from "mikrojs/fs";
|
|
238
|
+
globalThis.__exists = exists("/dummy.txt");
|
|
239
|
+
)");
|
|
240
|
+
bool was_exception = JS_IsException(ret);
|
|
241
|
+
bool file_exists = get_global_bool("__exists");
|
|
242
|
+
pub_fs_teardown();
|
|
243
|
+
|
|
244
|
+
TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
|
|
245
|
+
TEST_ASSERT_TRUE(file_exists);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
TEST_CASE("mikrojs/fs exists returns false for missing file", "[fs]") {
|
|
249
|
+
pub_fs_setup();
|
|
250
|
+
|
|
251
|
+
JSValue ret = pub_fs_eval(R"(
|
|
252
|
+
import { exists } from "mikrojs/fs";
|
|
253
|
+
globalThis.__exists = exists("/nonexistent.txt");
|
|
254
|
+
)");
|
|
255
|
+
bool was_exception = JS_IsException(ret);
|
|
256
|
+
bool file_exists = get_global_bool("__exists");
|
|
257
|
+
pub_fs_teardown();
|
|
258
|
+
|
|
259
|
+
TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
|
|
260
|
+
TEST_ASSERT_FALSE(file_exists);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* ── mkdir / readDir / rmdir ─────────────────────────────────────── */
|
|
264
|
+
|
|
265
|
+
TEST_CASE("mikrojs/fs mkdir, readDir, rmdir roundtrip", "[fs]") {
|
|
266
|
+
pub_fs_setup();
|
|
267
|
+
|
|
268
|
+
JSValue ret = pub_fs_eval(R"(
|
|
269
|
+
import { mkdir, readDir, writeFile, unlink, rmdir } from "mikrojs/fs";
|
|
270
|
+
const m = mkdir("/test_pub_dir");
|
|
271
|
+
const w = writeFile("/test_pub_dir/file.txt", "hi");
|
|
272
|
+
const entries = readDir("/test_pub_dir");
|
|
273
|
+
globalThis.__hasFile = entries.ok &&
|
|
274
|
+
entries.value.some(e => e.name === "file.txt" && e.isFile);
|
|
275
|
+
const u = unlink("/test_pub_dir/file.txt");
|
|
276
|
+
const r = rmdir("/test_pub_dir");
|
|
277
|
+
globalThis.__done = m.ok && w.ok && u.ok && r.ok;
|
|
278
|
+
)");
|
|
279
|
+
bool was_exception = JS_IsException(ret);
|
|
280
|
+
bool has_file = get_global_bool("__hasFile");
|
|
281
|
+
bool done = get_global_bool("__done");
|
|
282
|
+
pub_fs_teardown();
|
|
283
|
+
|
|
284
|
+
TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
|
|
285
|
+
TEST_ASSERT_TRUE(has_file);
|
|
286
|
+
TEST_ASSERT_TRUE(done);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/* ── unlink / rename ─────────────────────────────────────────────── */
|
|
290
|
+
|
|
291
|
+
TEST_CASE("mikrojs/fs unlink and rename", "[fs]") {
|
|
292
|
+
pub_fs_setup();
|
|
293
|
+
|
|
294
|
+
JSValue ret = pub_fs_eval(R"(
|
|
295
|
+
import { writeFile, rename, readFile, unlink } from "mikrojs/fs";
|
|
296
|
+
const w = writeFile("/test_pub_rename_src.txt", "rename me");
|
|
297
|
+
const mv = rename("/test_pub_rename_src.txt", "/test_pub_rename_dst.txt");
|
|
298
|
+
const r = readFile("/test_pub_rename_dst.txt", "utf-8");
|
|
299
|
+
unlink("/test_pub_rename_dst.txt");
|
|
300
|
+
globalThis.__ok = w.ok && mv.ok && r.ok;
|
|
301
|
+
if (r.ok) globalThis.__content = r.value;
|
|
302
|
+
)");
|
|
303
|
+
bool was_exception = JS_IsException(ret);
|
|
304
|
+
bool ok = get_global_bool("__ok");
|
|
305
|
+
char* content = get_global_string("__content");
|
|
306
|
+
pub_fs_teardown();
|
|
307
|
+
|
|
308
|
+
TEST_ASSERT_FALSE_MESSAGE(was_exception, "Module eval should not throw");
|
|
309
|
+
TEST_ASSERT_TRUE(ok);
|
|
310
|
+
TEST_ASSERT_EQUAL_STRING("rename me", content);
|
|
311
|
+
free(content);
|
|
312
|
+
}
|