From b590eaa2f64ead3384eadc6fe58f6358aa1a0478 Mon Sep 17 00:00:00 2001 From: Wojtek Kosior Date: Wed, 22 Dec 2021 16:39:34 +0100 Subject: reworked build system; added missing license notices --- compute_scripts.awk | 748 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 619 insertions(+), 129 deletions(-) mode change 100644 => 100755 compute_scripts.awk (limited to 'compute_scripts.awk') diff --git a/compute_scripts.awk b/compute_scripts.awk old mode 100644 new mode 100755 index 3d8a5b0..9edc56d --- a/compute_scripts.awk +++ b/compute_scripts.awk @@ -1,3 +1,5 @@ +#!/usr/bin/awk -f +# # SPDX-License-Identifier: CC0-1.0 # # Process javascript files and resolve dependencies between them @@ -15,195 +17,683 @@ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # CC0 1.0 Universal License for more details. -function read_file(filename, - imports_state, exports_state, line, record, result) { - imports_state = "not_started" - exports_state = "not_started" +BEGIN { + true = 1 + false = 0 +} + +BEGIN { + identifier_re = "[_a-zA-Z][_a-zA-Z0-9]*" + path_dir_re = "([-_a-zA-Z0-9][-._a-zA-Z0-9]*/)*" + path_ext_re = "(\\.[-_.a-zA-Z0-9]*)?" + path_re = "^" path_dir_re identifier_re path_ext_re "$" + + directive_args_patterns["IF"] = "^(NOT[[:space:]]+)?" identifier_re "$" + directive_args_patterns["ENDIF"] = "^$" + directive_args_patterns["ELSE"] = "^$" + directive_args_patterns["ELIF"] = "^(NOT[[:space:]]+)?" identifier_re "$" + directive_args_patterns["ERROR"] = "^.*$" + directive_args_patterns["COPY"] = "^[^[:space:]]+$" + directive_args_patterns["INCLUDE"] = "^[^[:space:]]+$" + directive_args_patterns["INCLUDE_VERBATIM"] = "^[^[:space:]]+$" + + AS_re = "AS[[:space:]]+" identifier_re + maybe_AS_re = "([[:space:]]+" AS_re ")?" + FROM_clause_re = identifier_re maybe_AS_re + more_FROM_clauses_re = "([[:space:]]*,[[:space:]]*" FROM_clause_re ")*" + FROM_IMPORT_re = "[^[:space:]]+[[:space:]]+IMPORT[[:space:]]+" + EXPORT_AS_re = ".*[^[:space:]][[:space:]]+" AS_re + + directive_args_patterns["IMPORT"] = "^[^[:space:]]+" maybe_AS_re "$" + directive_args_patterns["FROM"] = ("^" FROM_IMPORT_re FROM_clause_re \ + more_FROM_clauses_re "$") + directive_args_patterns["EXPORT"] = "^(" EXPORT_AS_re "|" identifier_re ")$" + + directive_args_patterns["LOADJS"] = "^[^[:space:]]+$" +} + +function validate_path(read_path, path, line) { + if (path !~ (path_re)) { + printf "ERROR: File path in %s does not match '%s': %s\n", + read_path, path_re, line > "/dev/stderr" + return 1 + } + + return 0 +} + +function identifier_from_path(path) { + sub("^" path_dir_re, "", path) + sub(path_ext_re "$", "", path) + + return path +} + +function last_token(line) { + sub("^.*[[:space:]]", "", line) + + return line +} + +function first_token(line) { + sub("[[:space:]].*$", "", line) + + return line +} + +function is_empty(array, key) { + for (key in array) + return false + return true +} - do { - result = (getline line < filename) +function clear_array(array, key) { + for (key in array) + delete array[key] +} + +function add_line(path, line, where) { + if (where != "amalgamation_root_file") + lines[path,++lines_count[path]] = line + + if (where != "non_amalgamation_file" && + path == js_to_amalgamate) + main_js_lines[++main_js_lines_count] = line +} + +BEGIN { + delete page_js_deps[0] + page_js_deps_count = 0 +} + +function process_file(path, read_path, mode, + line, result, line_part, directive, directive_args, + if_nesting, if_nesting_true, if_branch_processed) { + if (path in modes && modes[path] != mode) { + printf "ERROR: File %s used multiple times in different contexts\n", + path > "/dev/stderr" + return 1 + } + + if (mode == "html" && path == read_path) { + clear_array(page_js_deps) + page_js_deps_count = 0 + } + + modes[path] = mode + + if (!(path in reading)) { + if (path in lines_count) + return 0 + lines_count[path] + } + + reading[read_path] + + if (mode == "js" && path == read_path) { + add_line(path, "\"use strict\";") + add_line(path, "this.haketilo_exports = this.haketilo_exports || {};") + add_line(path, "this.haketilo_exports[\"" path "\"] = {};") + + add_line(path, "window.globalThis = this", "amalgamation_root_file") + + add_line(path, "") + + add_line(path, "(function() {", "non_amalgamation_file") + add_line(path, "var globalThis = this.haketilo_this", + "non_amalgamation_file") + add_line(path, "{", "non_amalgamation_file") + } + + while (true) { + result = (getline line < read_path) if (result < 0) { - printf "error reading %s", filename - exit 1 + printf "ERROR: Could not read %s\n", read_path > "/dev/stderr" + return 1 } + if (result == 0) + break - if (imports_state == "started" && - line ~ /^([[:space:]]*\*[[:space:]]+)?IMPORT[[:space:]]+[_a-zA-Z][_a-zA-Z0-9]*[[:space:]]*$/) { - record = line + if (line !~ /^#/) { + if (if_nesting_true == if_nesting) + add_line(path, line) + continue + } - sub(/^([[:space:]]*\*[[:space:]]+)?IMPORT[[:space:]]+/, "", record) - sub(/([[:space:]]+$)/, "", record) + while (line ~ /\\$/) { + sub(/\\$/, "", line) - imports[filename,++import_counts[filename]] = record + result = (getline line_part < read_path) + if (result < 0) { + printf "ERROR: Could not read %s\n", read_path > "/dev/stderr" + return 1 + } + if (result == 0) { + printf "ERROR: Unexpected EOF in %s\n", + read_path > "/dev/stderr" + return 1 + } + + line = line " " line_part + } + + directive = substr(line, 2) + sub(/[[:space:]].*$/, "", directive) + + if (directive !~ \ + /^(IF|ENDIF|ELSE|ELIF|ERROR|INCLUDE|INCLUDE_VERBATIM|COPY_FILE)$/ && + (mode != "js" || directive !~ /^(IMPORT|FROM|EXPORT)$/) && + (mode != "html" || directive !~ /^LOADJS$/) && + (mode != "manifest" || directive !~ /^(LOADJS|LOADHTML)$/)) { + printf "ERROR: Invalid # directive in %s: %s\n", + read_path, line > "/dev/stderr" + return 1 + } + + directive_args = line + sub(/^#[^[:space:]]*[[:space:]]*/, "", directive_args) + sub(/[[:space:]]*$/, "", directive_args) + + if (directive_args !~ directive_args_patterns[directive]) { + printf "ERROR: #%s arguments in %s do not match '%s': %s\n", + directive, read_path, directive_args_patterns[directive], line \ + > "/dev/stderr" + return 1 } - if (imports_state == "started" && - line ~ /^([[:space:]]*\*[[:space:]]+)?IMPORTS_END[[:space:]]*$/) - imports_state = "finished" - if (imports_state == "not_started" && - line ~ /^([[:space:]]*\*[[:space:]]+)?IMPORTS_START[[:space:]]*$/) - imports_state = "started" - - if (exports_state == "started" && - line ~ /^([[:space:]]*\*[[:space:]]+)?EXPORT[[:space:]]+[_a-zA-Z][_a-zA-Z0-9]*[[:space:]]*$/) { - record = line - - sub(/^([[:space:]]*\*[[:space:]]+)?EXPORT[[:space:]]+/, "", record) - sub(/([[:space:]]+$)/, "", record) - - if (record in exports) { - printf "ERROR: '%s' exported by both %s and %s\n", - exports[record], filename > "/dev/stderr" + + if (directive == "IF") { + if (if_nesting_true == if_nesting) { + if ((last_token(directive_args) in defines) == \ + (directive_args ~ /^[^[:space:]]+$/)) + if_nesting_true++ + else + if_branch_processed = false } - provides[record] = filename - exports[filename,++export_counts[filename]] = record + if_nesting++ + } else if (directive == "ENDIF") { + if (if_nesting == 0) { + printf "ERROR: Spurious #ENDIF in %s\n", + read_path > "/dev/stderr" + return 1 + } + + if (if_nesting_true == if_nesting) + if_nesting_true-- + + if_nesting-- + } else if (directive == "ELSE") { + if (if_nesting == 0) { + printf "ERROR: Spurious #ELSE in %s\n", + read_path > "/dev/stderr" + return 1 + } + + if (if_nesting == if_nesting_true + 1 && !if_branch_processed) { + if_nesting_true++ + } else if (if_nesting == if_nesting_true) { + if_branch_processed = true + if_nesting_true-- + } + } else if (directive == "ELIF") { + if (if_nesting == 0) { + printf "ERROR: Spurious #ELIF in %s\n", + read_path > "/dev/stderr" + return 1 + } + + if (if_nesting == if_nesting_true + 1 && !if_branch_processed && + (last_token(directive_args) in defines) == \ + (directive_args ~ /^[^[:space:]]+$/)) { + if_nesting_true++ + } else if (if_nesting == if_nesting_true) { + if_branch_processed = true + if_nesting_true-- + } + } else if (if_nesting_true != if_nesting) { + continue + } else if (directive == "ERROR") { + printf "ERROR: File %s says: %s\n", + read_path, directive_args > "/dev/stderr" + return 1 + } else if (directive == "INCLUDE") { + if (include_file(path, read_path, directive_args, line)) + return 1 + } else if (directive == "INCLUDE_VERBATIM") { + if (include_file(path, read_path, directive_args, line, true)) + return 1 + } else if (directive == "COPY_FILE") { + if (mark_copy_file(path, read_path, directive_args, line)) + return 1 + } else if (directive == "IMPORT") { + if (import_js_file(path, read_path, directive_args, line)) + return 1 + } else if (directive == "FROM") { + if (import_from_js_file(path, read_path, directive_args, line)) + return 1 + } else if (directive == "EXPORT") { + if (export_from_js_file(path, read_path, directive_args, line)) + return 1 + } else if (directive == "LOADJS") { + if (mode == "html") { + page_js_deps_count = \ + load_js_file(path, read_path, directive_args, line, + page_js_deps, page_js_deps_count) + if (page_js_deps_count < 1) + return 1 + } else if (mode == "manifest") { + if (load_js_file(path, read_path, directive_args, line) < 1) + return 1 + } + } else if (directive == "LOADHTML") { + if (load_html_file(path, read_path, directive_args, line)) + return 1 } - if (exports_state == "started" && - line ~ /^([[:space:]]*\*[[:space:]]+)?EXPORTS_END[[:space:]]*$/) - exports_state = "finished" - if (exports_state == "not_started" && - line ~ /^([[:space:]]*\*[[:space:]]+)?EXPORTS_START[[:space:]]*$/) - exports_state = "started" - } while (result > 0) + } + + close(read_path) - if (imports_state == "started") { - printf "ERROR: Unclosed IMPORTS list in '%s'\n", filename \ - > "/dev/stderr" - exit 1 + if (if_nesting) { + printf "ERROR: Unterminated #IF in %s\n", read_path > "/dev/stderr" + return 1 } - if (exports_state == "started") { - printf "ERROR: Unclosed EXPORTS list in '%s'\n", filename \ - > "/dev/stderr" - exit 1 + if (mode == "js" && path == read_path) { + add_line(path, "}", "non_amalgamation_file") + add_line(path, "}).call({", "non_amalgamation_file") + add_line(path, " haketilo_exports: this.haketilo_exports,", + "non_amalgamation_file") + add_line(path, " haketilo_this: this", + "non_amalgamation_file") + add_line(path, "});", "non_amalgamation_file") } - close(filename) + delete reading[read_path] } -function print_file(filename, line) { - while ((getline line < filename) > 0) - print(line) +function include_file(root_path, read_path, included_path, line, verbatim, + read_line, result) { + if (validate_path(read_path, included_path, line)) + return 1 + + if (included_path in reading) { + printf "ERROR: Inclusion loop when including %s in %s\n", + included_path, read_path > "/dev/stderr" + return 1 + } + + if (verbatim) { + while(true) { + result = (getline read_line < included_path) + if (result > 0) + add_line(root_path, read_line) + else + break + } + + if (result == 0) { + close(included_path) + return 0 + } - close(filename) + printf "ERROR: Could not read %s\n", included_path > "/dev/stderr" + } else { + if (process_file(root_path, included_path, modes[root_path]) == 0) + return 0 + } + + printf " when including %s in %s\n", + included_path, read_path > "/dev/stderr" + + return 1 } -function print_imports_code(filename, declarator, i, count, import_name) { - count = import_counts[filename] - for (i = 1; i <= count; i++) { - import_name = imports[filename,i] - printf "%s %s = window.haketilo_exports.%s;\n", - declarator, import_name, import_name +function mark_copy_file(root_path, read_path, copied_path, line) { + if (validate_path(read_path, copied_path, line)) + return 1 + + to_copy[copied_path] + + return 0 +} + +function satisfy_import(root_path, imported_path, as, what, + added_line, description, count) { + if ((root_path,as) in imports_from) { + printf "ERROR: Multiple items imported under the name '%s' in %s\n", + as, root_path > "/dev/stderr" + return 1 } + + added_line = " " as " = haketilo_exports[\"" imported_path "\"]" + if (what) + added_line = added_line "." what + + add_line(root_path, "const" added_line ";", "non_amalgamation_file") + add_line(root_path, "let" added_line ";", "amalgamation_root_file") + + count = ++import_counts[root_path] + + imports_as [root_path,count] = as + imports_from[root_path,as] = imported_path + imports_what[root_path,as] = what + + if (what) + description = "'" what "' from " imported_path + else + description = imported_path + + description = description " needed by " root_path + + if (imported_path in reading) { + printf "ERROR: dependency loop when importing %s\n", + description > "/dev/stderr" + return 1 + } else if (process_file(imported_path, imported_path, "js")) { + printf " when importing %s\n", description > "/dev/stderr" + return 1 + } + + if (what && !((imported_path,what) in exports)) { + printf "ERROR: %s doesn't export '%s' needed by %s\n", + imported_path, what, root_path > "/dev/stderr" + return 1 + } + + return 0 } -function print_exports_code(filename, i, count, export_name) { - count = export_counts[filename] - for (i = 1; i <= count; i++) { - export_name = exports[filename,i] - printf "window.haketilo_exports.%s = %s;\n", export_name, export_name +function import_js_file(root_path, read_path, directive_args, line, + imported_path, as) { + imported_path = first_token(directive_args) + if (validate_path(read_path, imported_path, line)) + return 1 + + if (line ~ (AS_re "$")) + as = last_token(directive_args) + else + as = identifier_from_path(imported_path) + + return satisfy_import(root_path, imported_path, as) +} + +function import_from_js_file(root_path, read_path, directive_args, line, + imported_path, args_copy, FROM_clause, as) { + imported_path = first_token(directive_args) + if (validate_path(read_path, imported_path, line)) + return 1 + + args_copy = directive_args + sub("^" FROM_IMPORT_re, "", args_copy) + args_copy = "," args_copy + + while (args_copy ~ /,/) { + sub(/^[^,]*,[[:space:]]*/, "", args_copy) + + FROM_clause = args_copy + sub(/[[:space:]]*,.*$/, "", FROM_clause) + + if (satisfy_import(root_path, imported_path, + last_token(FROM_clause), first_token(FROM_clause))) + return 1 } + + return 0 } -function partially_wrap_file(filename, declarator) { - if (!declarator) - declarator = "const" +function export_from_js_file(root_path, read_path, directive_args, line, + as, exported_item, added_line) { + as = last_token(directive_args) + + if (directive_args ~ ("^" identifier_re "$")) { + exported_item = as + } else { + exported_item = directive_args + sub("[[:space:]]+" AS_re "$", "", exported_item) + } + + if ((root_path,as) in exports) { + printf "ERROR: Multiple values exported under the name '%s' in %s\n", + as, root_path > "/dev/stderr" + return 1 + } - print_imports_code(filename, declarator) - printf "\n\n" + added_line = \ + "this.haketilo_exports[\"" root_path "\"]." as " = (" exported_item ");" + add_line(root_path, added_line) - print_file(filename) + exports[root_path,as] - printf "\n\n" - print_exports_code(filename) + return 0 } -function wrap_file(filename) { - print "\"use strict\";\n\n({fun: (function() {\n" +function compute_deps(js_path, dependencies, count, dependencies_added, + i_max, i, as, next_path) { + delete dependencies_added[0] - partially_wrap_file(filename) + if (process_file(js_path, js_path, "js")) + return 0 - print "\n})}).fun();" + i_max = import_counts[js_path] + for (i = 1; i <= i_max; i++) { + as = imports_as[js_path,i] + next_path = imports_from[js_path,as] + if (next_path in dependencies_added) + continue + + count = compute_deps(next_path, dependencies, count, dependencies_added) + if (count < 1) + return 0 + } + + dependencies_added[js_path] + dependencies[++count] = js_path + + return count } -function compute_dependencies(filename, i, count, import_name, next_file) { - if (processed[filename] == "used") +# Here js_deps and js_deps_count are optional args, used when loading scripts +# into an HTML page to avoid having th same script loaded twice in multiple +# places. +function load_js_file(root_path, read_path, loaded_path, line, + js_deps, js_deps_count, + js_deps_already_added, i, added_line) { + delete js_deps[""] + delete js_deps_already_added[0] + + if (validate_path(read_path, loaded_path, line)) return 0 - if (processed[filename] == "on_stack") { - printf "import loop on %s\n", filename > "/dev/stderr" + for (i = 1; i <= js_deps_count; i++) + js_deps_already_added[js_deps[i]] + + i = js_deps_count + + js_deps_count = compute_deps(loaded_path, js_deps, + js_deps_count, js_deps_already_added) + + if (js_deps_count < 1) { + printf " when loading %s from %s\n", + loaded_path, read_path > "/dev/stderr" + return 0 + } + + while (++i <= js_deps_count) { + if (modes[root_path] == "html") { + added_line = "" + } else { #if (modes[root_path] == "manifest") { + added_line = "\"" js_deps[i] "\"" + if (i != js_deps_count) + added_line = added_line "," + } + add_line(root_path, added_line) + } + + return js_deps_count +} + +function load_html_file(root_path, read_path, loaded_path, line) { + if (validate_path(read_path, loaded_path, line)) + return 1 + + if (process_file(loaded_path, loaded_path, "html")) { + printf " when loading %s from %s\n", + loaded_path, read_path, line > "/dev/stderr" return 1 } - processed[filename] = "on_stack" + return 0 +} - count = import_counts[filename] - for (i = 1; i <= count; i++) { - import_name = imports[filename,i] - if (!(import_name in provides)) { - printf "nothing exports %s, required by %s\n", - import_name, filename > "/dev/stderr" - return 1 - } +function print_amalgamation(js_deps, js_deps_count, + js_dep_nr, path, max_line_nr, line_nr) { + delete js_deps[0] - if (compute_dependencies(provides[import_name]) > 0) { - printf "when satisfying %s for %s\n", - import_name, filename > "/dev/stderr" - return 1 - } + js_deps_count = compute_deps(js_to_amalgamate, js_deps, 0) + if (js_deps_count < 1) + return 1 + + # '<' instead of '<=' because we print the main js file below instead + for (js_dep_nr = 1; js_dep_nr < js_deps_count; js_dep_nr++) { + path = js_deps[js_dep_nr] + max_line_nr = lines_count[path] + + for (line_nr = 1; line_nr <= max_line_nr; line_nr++) + print lines[path, line_nr] } - processed[filename] = "used" - print filename + for (line_nr = 1; line_nr <= main_js_lines_count; line_nr++) + print main_js_lines[line_nr] return 0 } function print_usage() { - printf "usage: %2 compute_scripts.awk script_dependencies|wrapped_code|partially_wrapped_code FILENAME[...]\n", + printf "USAGE: %s compute_scripts.awk -- [-D PREPROCESSOR_DEFINITION]... [-M manifest/to/process/manifest.json]... [-H html/to/process.html]... [-J js/to/process.js]... [--help|-h] [--output-dir=./build] [--write-js-deps] [--write-html-deps] [--output=files-to-copy|--output=amalgamate-js:js/to/process.js]\n", ARGV[0] > "/dev/stderr" - exit 1 } -function mock_exports_init() { - provides["browser"] = "exports_init.js" - provides["is_chrome"] = "exports_init.js" - provides["is_mozilla"] = "exports_init.js" - provides["initial_data"] = "exports_init.js" - - processed["exports_init.js"] = "used" +BEGIN { + option_arg_patterns["D"] = "^" identifier_re "$" + option_arg_patterns["M"] = path_re + option_arg_patterns["H"] = path_re + option_arg_patterns["J"] = path_re } -BEGIN { - operation = ARGV[1] +function main(i, path, letter, dir, max_line_nr, js_deps, js_deps_count) { + output_dir = "./build" + write_js_deps = false + write_html_deps = false - if (ARGC < 3) - print_usage() + output = "" + js_to_amalgamate = "" + delete main_js_lines[0] - root_filename = ARGV[2] + delete manifests_to_process[0] + delete html_to_process[0] + delete js_to_process[0] - for (i = 2; i < ARGC; i++) - filenames[ARGV[i]] + delete explicitly_requested[0] - mock_exports_init() + for (i = 1; i < ARGC; i++) { + if (ARGV[i] ~ /^-[DMHJ]$/) { + letter = substr(ARGV[i++], 2) + if (i == ARGC || ARGV[i] !~ option_arg_patterns[letter]) { + printf "ERROR: '-%s' option should be followed by an argument matching '%s'\n", + letter, option_arg_patterns[letter] > "/dev/stderr" + return 1 + } - for (filename in filenames) { - # A filename is allowed to appear multiple times in the list. - # Let's only process it once. - if (!(filename in processed)) - read_file(filename) - processed[filename] = "not_used" + if (letter == "D") + defines[ARGV[i]] + else + explicitly_requested[ARGV[i]] + + if (letter == "M") + manifests_to_process[ARGV[i]] + if (letter == "H") + html_to_process[ARGV[i]] + if (letter == "J") + js_to_process[ARGV[i]] + } else if (ARGV[i] ~ /^-(-help|h)$/ ) { + print_usage() + return 0 + } else if (ARGV[i] ~ /^--output-dir=/) { + output_dir = ARGV[i] + sub(/^--output-dir=/, "", output_dir) + } else if (ARGV[i] ~ /^--write-js-deps$/) { + write_js_deps = true + } else if (ARGV[i] ~ /^--write-html-deps$/) { + write_html_deps = true + } else if (ARGV[i] ~ /^--output=files-to-copy$/) { + output = "files-to-copy" + } else if (ARGV[i] ~ /^--output=amalgamate-js:/) { + output = "amalgamate-js" + js_to_amalgamate = ARGV[i] + sub(/^--output=amalgamate-js:/, "", js_to_amalgamate) + if (js_to_amalgamate !~ path_re) { + printf "ERROR: amalgamate-js path does not match '%s': %s\n", + path_re, js_to_amalgamate > "/dev/stderr" + return 1 + } + } else { + printf "ERROR: Unknown option '%s'\n", ARGV[i] > "/dev/stderr" + print_usage() + return 1 + } } - if (operation == "script_dependencies") { - print("exports_init.js") - if (compute_dependencies(root_filename) > 0) - exit 1 - } else if (operation == "partially_wrapped_code") { - partially_wrap_file(root_filename, "let") - } else if (operation == "wrapped_code") { - wrap_file(root_filename) - } else { - print_usage() + if (is_empty(explicitly_requested) && output != "amalgamate-js") { + explicitly_requested["manifest.json"] + manifests_to_process["manifest.json"] } + + for (path in manifests_to_process) { + if (process_file(path, path, "manifest")) + return 1 + } + for (path in html_to_process) { + if (process_file(path, path, "html")) + return 1 + } + for (path in js_to_process) { + if (process_file(path, path, "js")) + return 1 + } + + for (path in lines_count) { + if (!(path in explicitly_requested) && + !(modes[path] == "js" && write_js_deps) && + !(modes[path] == "html" && write_html_deps)) + continue + + dir = path + sub(/[^/]*$/, "", dir) + dir = output_dir "/" dir + sub("'", "'\\''", dir) + + system("mkdir -p '" dir "'") + + printf "" > (output_dir "/" path) + + max_line_nr = lines_count[path] + for (i = 1; i <= max_line_nr; i++) + print lines[path, i] >> (output_dir "/" path) + } + + if (output == "files-to-copy") { + for (path in to_copy) + print path + } + + if (output == "amalgamate-js") { + if (print_amalgamation()) + return 1 + } + + return 0 +} + +BEGIN { + exit main() } -- cgit v1.2.3