tacker

a simple web bundler
git clone https://tongong.net/git/tacker.git
Log | Files | Refs | README

helpers.ha (3512B)


      1 use fs;
      2 use io;
      3 use os;
      4 use slices;
      5 use strings;
      6 
      7 // All bundled files must be within this directory so that malicious modules
      8 // cannot require arbitrary files on the file system.
      9 let basepath: str = "";
     10 @fini fn fini() void = free(basepath);
     11 
     12 // Cut a string to the last "/".
     13 // Return value is borrowed from the input.
     14 fn parent_dir(path: str) str = {
     15 	const bytes = strings::toutf8(path);
     16 	let i = len(bytes) - 1;
     17 	for (bytes[i] != '/') i -= 1;
     18 	return strings::fromutf8(bytes[..(i+1)]);
     19 };
     20 
     21 // Apply os::realpath and os::resolve.
     22 fn realpath_resolve(path: str) str = {
     23 	const p = match (os::realpath(path)) {
     24 	case let p: str => yield p;
     25 	case let p: fs::error =>
     26 		fixed_fatalf("{}: file does not exist.", path);
     27 		yield ""; // unreachable
     28 	};
     29 	return os::resolve(p);
     30 };
     31 
     32 // path: to be resolved
     33 // from: path to the file (or directory) where the reference was found.
     34 // Return value has to be freed.
     35 fn resolve_path(path: str, from: str) str = {
     36 	if (strings::hasprefix(path, "http://") ||
     37 			strings::hasprefix(path, "https://")) {
     38 		fixed_fatalf("{}: bundling of external resources is not allowed.",
     39 			path);
     40 	};
     41 	// directory path is relativ to base
     42 	// ends with "/"
     43 	const base = if (strings::hasprefix(path, "./") ||
     44 			strings::hasprefix(path, "../")) {
     45 		yield parent_dir(from);
     46 	} else {
     47 		yield basepath;
     48 	};
     49 	const r = strings::join("", base, path);
     50 	defer free(r);
     51 	const r = strings::dup(realpath_resolve(r));
     52 	if (!strings::hasprefix(r, basepath))
     53 		fixed_fatalf("{}: file path violates the base path \"{}\".",
     54 			r, basepath);
     55 	return r;
     56 };
     57 
     58 // Works like resolve_path() but adds a .js extension if there is none
     59 fn resolve_path_require(path: str, from: str) str = {
     60 	return if (strings::hassuffix(path, ".js"))
     61 		resolve_path(path, from)
     62 	else {
     63 		const p = strings::join("", path, ".js");
     64 		const res = resolve_path(p, from);
     65 		free(p);
     66 		yield res;
     67 	};
     68 };
     69 
     70 // Return index of the last dot in the filename or -1 if the file contains no
     71 // dot.
     72 fn lastdotindex(filename: str) int = {
     73 	const filename = strings::toutf8(filename);
     74 	let index = (len(filename) - 1): int;
     75 	for (index >= 0 && filename[index] != '.') {
     76 		if (filename[index] == '/') return -1;
     77 		index -= 1;
     78 	};
     79 	return index;
     80 };
     81 
     82 // return value has to be freed.
     83 fn file_name_bundled(filename: str) str = {
     84 	let lastdot = lastdotindex(filename);
     85 	// files without extension get the .bundle at the end
     86 	if (lastdot == -1) lastdot = len(filename): int;
     87 
     88 	const output = strings::dup(filename);
     89 	const output = strings::toutf8(output);
     90 
     91 	const ext = strings::toutf8(".bundle");
     92 	let bptr: [7]*void = [&ext: *void ...];
     93 	for (let i = 0z; i < len(ext); i += 1) {
     94 		bptr[i] = &ext[i];
     95 	};
     96 	slices::insertinto(&output: *[]void, size(u8), lastdot: size, bptr...);
     97 	return strings::fromutf8(output);
     98 };
     99 
    100 @test fn file_name_bundled() void = {
    101 	assert(file_name_bundled("test.js") == "test.bundle.js");
    102 	assert(file_name_bundled("test.dot.js") == "test.dot.bundle.js");
    103 	assert(file_name_bundled("no-ext") == "no-ext.bundle");
    104 	assert(file_name_bundled("./dir.a/no-ext") == "./dir.a/no-ext.bundle");
    105 	assert(file_name_bundled("./test.dir/ütf8.html") ==
    106 		"./test.dir/ütf8.bundle.html");
    107 };
    108 
    109 // A file to write useless output to (like /dev/null)
    110 const black_hole: io::handle = &black_hole_s;
    111 const black_hole_s: io::stream = &black_hole_v;
    112 const black_hole_v: io::vtable = io::vtable {
    113 	reader = null,
    114 	writer = &black_hole_write,
    115 	...
    116 };
    117 fn black_hole_write(s: *io::stream, buf: const []u8) (size | io::error) = {
    118 	return len(buf);
    119 };