Source io.nas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# Reads and returns a complete file as a string
var readfile = func(file) {
if ((var st = stat(file)) == nil)
die("Cannot stat file: " ~ file);
var sz = st[7];
var buf = bits.buf(sz);
read(open(file), buf, sz);
return buf;
}
# basename(<path>), dirname(<path>)
#
# Work like standard Unix commands: basename returns the file name from a given
# path, and dirname returns the directory part.
var basename = func(path) {
split("/", string.normpath(path))[-1];
};
var dirname = func(path) {
path = string.normpath(path);
substr(path, 0, size(path) - size(basename(path)));
};
var is_directory = func(path) {
var tmp = stat(path);
if (tmp != nil and tmp[11] == "dir") return 1;
else return 0;
};
var is_regular_file = func(path) {
var tmp = stat(path);
if (tmp != nil and tmp[11] == "reg") return 1;
else return 0;
};
# <path> the path that should be searched for subdirectories
# returns a vector of subdirectory names
var subdirectories = func(path) {
if (!is_directory(path))
return;
var list = subvec(directory(path), 2);
var subdirs = [];
foreach (var entry; list) {
if (is_directory(path~"/"~entry)) {
append(subdirs, entry);
}
}
return subdirs;
}
# include(<filename>)
#
# Loads and executes a Nasal file in place. The file is searched for in the
# calling script directory and in standard FG directories (in that order).
#
# Examples:
#
# io.include("Aircraft/Generic/library.nas");
# io.include("my_other_file.nas");
var include = func(file) {
file = string.normpath(file);
var clr = caller();
var (ns, fn, fl) = clr;
var local_file = dirname(fl) ~ file;
var path = (stat(local_file) != nil)? local_file : resolvepath(file);
if (path == "") die("File not found: ", file);
var module = "__" ~ path ~ "__";
if (contains(ns, module))
return;
var code = call(compile, [readfile(path), path], var err = []);
if (size(err)) {
if (find("Parse error:", err[0]) < 0)
die(err[0]);
else
die(sprintf("%s\n in included file: %s", err[0], path));
}
ns[module] = "included";
call(bind(code, ns, fn), [], nil, ns);
}
# Loads Nasal file into namespace and executes it. The namespace
# (module name) is taken from the optional second argument, or
# derived from the Nasal file's name.
#
# Usage: io.load_nasal(<filename> [, <modulename>]);
#
# Example:
#
# io.load_nasal(getprop("/sim/fg-root") ~ "/Local/test.nas");
# io.load_nasal("/tmp/foo.nas", "test");
#
var load_nasal = func(file, module = nil) {
if (module == nil)
module = split(".", split("/", file)[-1])[0];
logprint(LOG_DEBUG, "loading ", file, " into namespace ", module);
if (!contains(globals, module))
globals[module] = {};
elsif (!ishash(globals[module]))
die("io.load_nasal(): namespace '" ~ module ~ "' already in use, but not a hash");
var code = call(func compile(readfile(file), file), nil, var err = []);
if (size(err)) {
if (substr(err[0], 0, 12) == "Parse error:") { # hack around Nasal feature
var e = split(" at line ", err[0]);
if (size(e) == 2)
err[0] = string.join("", [e[0], "\n at ", file, ", line ", e[1], "\n "]);
}
for (var i = 1; (var c = caller(i)) != nil; i += 1)
err ~= subvec(c, 2, 2);
debug.printerror(err);
return 0;
}
call(bind(code, globals), nil, nil, globals[module], err);
debug.printerror(err);
return !size(err);
}
# Load XML file in FlightGear's native <PropertyList> format.
# If the second, optional target parameter is set, then the properties
# are loaded to this node in the global property tree. Otherwise they
# are returned as a separate props.Node tree. Returns the data as a
# props.Node on success or nil on error.
#
# Usage: io.read_properties(<filename> [, <props.Node or property-path>]);
#
# Examples:
#
# var target = props.globals.getNode("/sim/model");
# io.read_properties("/tmp/foo.xml", target);
#
# var data = io.read_properties("/tmp/foo.xml", "/sim/model");
# var data = io.read_properties("/tmp/foo.xml");
#
var read_properties = func(path, target = nil) {
var args = props.Node.new({ filename: path });
if (target == nil) {
var ret = args.getNode("data", 1);
} elsif (isa(target, props.Node)) {
args.getNode("targetnode", 1).setValue(target.getPath());
var ret = target;
} else {
args.getNode("targetnode", 1).setValue(target);
var ret = props.globals.getNode(target, 1);
}
return fgcommand("loadxml", args) ? ret : nil;
}
# Load XML file in FlightGear's native <PropertyList> format.
# file will be located in the airport-scenery directories according to
# ICAO and filename, i,e in Airports/I/C/A/ICAO.filename.xml
# If the second, optional target parameter is set, then the properties
# are loaded to this node in the global property tree. Otherwise they
# are returned as a separate props.Node tree. Returns the data as a
# props.Node on success or nil on error.
#
# Usage: io.read_airport_properties(<icao>, <filename> [, <props.Node or property-path>]);
#
# Examples:
#
# var data = io.read_properties("KSFO", "rwyuse");
#
var read_airport_properties = func(icao, fname, target = nil) {
var args = props.Node.new({ filename: fname, icao:icao });
if (target == nil) {
var ret = args.getNode("data", 1);
} elsif (isa(target, props.Node)) {
args.getNode("targetnode", 1).setValue(target.getPath());
var ret = target;
} else {
args.getNode("targetnode", 1).setValue(target);
var ret = props.globals.getNode(target, 1);
}
return fgcommand("loadxml", args) ? ret : nil;
}
# Write XML file in FlightGear's native <PropertyList> format.
# Returns the filename on success or nil on error. If the source
# is a props.Node that refers to a node in the main tree, then
# the data are directly written from the tree, yielding a more
# accurate result. Otherwise the data need to be copied first,
# which may slightly change node types (FLOAT becomes DOUBLE etc.)
#
# Usage: io.write_properties(<filename>, <props.Node or property-path>);
#
# Examples:
#
# var data = props.Node.new({ a:1, b:2, c:{ d:3, e:4 } });
# io.write_properties("/tmp/foo.xml", data);
# io.write_properties("/tmp/foo.xml", "/sim/model");
#
var write_properties = func(path, prop) {
var args = props.Node.new({ filename: path });
# default attributes of a new node plus the lowest unused bit
var attr = args.getAttribute() + args.getAttribute("last") * 2;
props.globals.setAttribute(attr);
if (isa(prop, props.Node)) {
for (var root = prop; (var p = root.getParent()) != nil;)
root = p;
if (root.getAttribute() == attr)
args.getNode("sourcenode", 1).setValue(prop.getPath());
else
props.copy(prop, args.getNode("data", 1), 1);
} else {
args.getNode("sourcenode", 1).setValue(prop);
}
return fgcommand("savexml", args) ? path : nil;
}
# The following two functions are for reading generic XML files into
# the property tree and for writing them from there to the disk. The
# built-in fgcommands (load, save, loadxml, savexml) are for FlightGear's
# own <PropertyList> XML files only, as they only handle a limited
# number of very specific attributes. The io.readxml() loader turns
# attributes into regular children with a configurable prefix prepended
# to their name, while io.writexml() turns such nodes back into
# attributes. The two functions have their own limitations, but can
# easily get extended to whichever needs. The underlying parsexml()
# command will handle any XML file.
# Reads an XML file from an absolute path and returns it as property
# tree. All nodes will be of type STRING. Data are only written to
# leafs. Attributes are written as regular nodes with the optional
# prefix prepended to the name. If the prefix is nil, then attributes
# are ignored. Returns nil on error.
#
var readxml = func(path, prefix = "___") {
var stack = [[{}, ""]];
var node = props.Node.new();
var tree = node; # prevent GC
var start = func(name, attr) {
var index = stack[-1][0];
if (!contains(index, name))
index[name] = 0;
node = node.getChild(name, index[name], 1);
if (prefix != nil)
foreach (var n; keys(attr))
node.getNode(prefix ~ n, 1).setValue(attr[n]);
index[name] += 1;
append(stack, [{}, ""]);
}
var end = func(name) {
var buf = pop(stack);
if (!size(buf[0]) and size(buf[1]))
node.setValue(buf[1]);
node = node.getParent();
}
var data = func(d) stack[-1][1] ~= d;
return parsexml(path, start, end, data) == nil ? nil : tree;
}
# Writes a property tree as returned by readxml() to a file. Children
# with name starting with <prefix> are again turned into attributes of
# their parent. <node> must contain exactly one child, which will
# become the XML file's outermost element.
#
var writexml = func(path, node, indent = "\t", prefix = "___") {
var root = node.getChildren();
if (!size(root))
die("writexml(): tree doesn't have a root node");
if (substr(path, -4) != ".xml")
path ~= ".xml";
var file = open(path, "w");
write(file, "<?xml version=\"1.0\"?>\n\n");
var writenode = func(n, ind = "") {
var name = n.getName();
var name_attr = name;
var children = [];
foreach (var c; n.getChildren()) {
var a = c.getName();
if (substr(a, 0, size(prefix)) == prefix)
name_attr ~= " " ~ substr(a, size(prefix)) ~ '="' ~ c.getValue() ~ '"';
else
append(children, c);
}
if (size(children)) {
write(file, ind ~ "<" ~ name_attr ~ ">\n");
foreach (var c; children)
writenode(c, ind ~ indent);
write(file, ind ~ "</" ~ name ~ ">\n");
} elsif ((var value = n.getValue()) != nil) {
write(file, ind ~ "<" ~ name_attr ~ ">" ~ value ~ "</" ~ name ~ ">\n");
} else {
write(file, ind ~ "<" ~ name_attr ~ "/>\n");
}
}
writenode(root[0]);
close(file);
if (size(root) != 1)
die("writexml(): tree has more than one root node");
}