Further module system revamp (fix #659)
To import a module now use:
# Import module.jq file:
import "relative/path/to/module" as foo;
# Use the module's defs as foo::<def-name>
To import a JSON file:
# Read file.json:
import "relative/path/to/file" as $foo;
#
# Use as $foo::foo
Using `-L` now drops the builtin library path and appends the requested
path to the empty array (or the result of an earlier `-L`).
Support for the `$JQ_LIBRARY_PATH` environment variable has been
removed.
This commit is contained in:
1 parent
7dc34b92af
commit
ae7f8d6ab9
13 files changed
+351
-207
No files matched your search
@@ -21,33 +21,71 @@ struct lib_loading_state {
|
||||
block *defs;
|
||||
uint64_t ct;
|
||||
};
|
||||
static int load_library(jq_state *jq, jv lib_path, block *out_block, struct lib_loading_state *lib_state);
|
||||
static int load_library(jq_state *jq, jv lib_path, int is_data, int raw,
|
||||
const char *as, block *out_block,
|
||||
struct lib_loading_state *lib_state);
|
||||
|
||||
|
||||
// Given a lib_path to search first, creates a chain of search paths
|
||||
// in the following order:
|
||||
// 1. lib_path
|
||||
// 2. -L paths passed in on the command line (from jq_state*)
|
||||
// 3. JQ_LIBRARY_PATH environment variable
|
||||
jv build_lib_search_chain(jq_state *jq, jv search_path) {
|
||||
// 2. -L paths passed in on the command line (from jq_state*) or builtin list
|
||||
static jv build_lib_search_chain(jq_state *jq, jv search_path, jv jq_origin, jv lib_origin) {
|
||||
assert(jv_get_kind(search_path) == JV_KIND_ARRAY);
|
||||
jv expanded = jv_array();
|
||||
jv expanded_elt;
|
||||
jv_array_foreach(search_path, i, path) {
|
||||
path = expand_path(path);
|
||||
if (jv_is_valid(path))
|
||||
expanded = jv_array_append(expanded, expand_path(path));
|
||||
else
|
||||
jv_free(path);
|
||||
if (strcmp(".",jv_string_value(path)) == 0) {
|
||||
expanded_elt = jv_copy(path);
|
||||
} else if (jv_get_kind(lib_origin) == JV_KIND_STRING &&
|
||||
strncmp("./", jv_string_value(path),sizeof("./")-1) == 0) {
|
||||
expanded_elt = jv_string_fmt("%s/%s",
|
||||
jv_string_value(lib_origin),
|
||||
jv_string_value(path) + sizeof ("./") - 1);
|
||||
} else if (strncmp("$ORIGIN/",jv_string_value(path),sizeof("$ORIGIN/")-1) == 0) {
|
||||
expanded_elt = jv_string_fmt("%s/%s",
|
||||
jv_string_value(jq_origin),
|
||||
jv_string_value(path) + sizeof ("$ORIGIN/") - 1);
|
||||
} else {
|
||||
expanded_elt = expand_path(path);
|
||||
if (!jv_is_valid(expanded_elt)) {
|
||||
jv_free(search_path);
|
||||
jv_free(expanded);
|
||||
jv_free(path);
|
||||
return expanded_elt;
|
||||
}
|
||||
path = jv_invalid();
|
||||
}
|
||||
expanded = jv_array_append(expanded, expanded_elt);
|
||||
jv_free(path);
|
||||
}
|
||||
jv_free(jq_origin);
|
||||
jv_free(lib_origin);
|
||||
jv_free(search_path);
|
||||
expanded = jv_array_concat(expanded, jq_get_lib_dirs(jq));
|
||||
return expanded;
|
||||
}
|
||||
|
||||
static jv name2relpath(jv name) {
|
||||
jv components = jv_string_split(jv_copy(name), jv_string("::"));
|
||||
// Doesn't actually check that name not be an absolute path; we could
|
||||
// just always prepend "./"!
|
||||
static jv validate_relpath(jv name) {
|
||||
const char *s = jv_string_value(name);
|
||||
if (strchr(s, '\\')) {
|
||||
jv res = jv_invalid_with_msg(jv_string_fmt("Modules must be named by relative paths using '/', not '\\' (%s)", s));
|
||||
jv_free(name);
|
||||
return res;
|
||||
}
|
||||
jv components = jv_string_split(jv_copy(name), jv_string("/"));
|
||||
jv rp = jv_array_get(jv_copy(components), 0);
|
||||
components = jv_array_slice(components, 1, jv_array_length(jv_copy(components)));
|
||||
jv_array_foreach(components, i, x) {
|
||||
if (!strcmp(jv_string_value(x), "..")) {
|
||||
jv_free(x);
|
||||
jv_free(rp);
|
||||
jv_free(components);
|
||||
jv res = jv_invalid_with_msg(jv_string_fmt("Relative paths to modules may not traverse to parent directories (%s)", s));
|
||||
jv_free(name);
|
||||
return res;
|
||||
}
|
||||
if (i > 0 && jv_equal(jv_copy(x), jv_array_get(jv_copy(components), i - 1))) {
|
||||
jv_free(x);
|
||||
jv_free(rp);
|
||||
@@ -64,22 +102,33 @@ static jv name2relpath(jv name) {
|
||||
return rp;
|
||||
}
|
||||
|
||||
static jv find_lib(jq_state *jq, jv lib_name, jv lib_search_path) {
|
||||
assert(jv_get_kind(lib_search_path) == JV_KIND_ARRAY);
|
||||
assert(jv_get_kind(lib_name) == JV_KIND_STRING);
|
||||
// Assumes name has been validated
|
||||
static jv jv_basename(jv name) {
|
||||
const char *s = jv_string_value(name);
|
||||
const char *p = strrchr(s, '/');
|
||||
if (!p)
|
||||
return name;
|
||||
jv res = jv_string_fmt("%s", p);
|
||||
jv_free(name);
|
||||
return res;
|
||||
}
|
||||
|
||||
jv rel_path = name2relpath(jv_copy(lib_name));
|
||||
if (!jv_is_valid(rel_path)) {
|
||||
jv_free(lib_name);
|
||||
return rel_path;
|
||||
}
|
||||
// Asummes validated relative path to module
|
||||
static jv find_lib(jq_state *jq, jv rel_path, jv search, const char *suffix, jv jq_origin, jv lib_origin) {
|
||||
if (jv_get_kind(search) != JV_KIND_ARRAY)
|
||||
return jv_invalid_with_msg(jv_string_fmt("Module search path must be an array"));
|
||||
if (jv_get_kind(rel_path) != JV_KIND_STRING)
|
||||
return jv_invalid_with_msg(jv_string_fmt("Module path must be a string"));
|
||||
|
||||
struct stat st;
|
||||
int ret;
|
||||
|
||||
jv lib_search_paths = build_lib_search_chain(jq, lib_search_path);
|
||||
// Ideally we should cache this somewhere
|
||||
search = build_lib_search_chain(jq, search, jq_origin, lib_origin);
|
||||
|
||||
jv_array_foreach(lib_search_paths, i, spath) {
|
||||
jv bname = jv_basename(jv_copy(rel_path));
|
||||
|
||||
jv_array_foreach(search, i, spath) {
|
||||
if (jv_get_kind(spath) == JV_KIND_NULL) {
|
||||
jv_free(spath);
|
||||
break;
|
||||
@@ -89,43 +138,44 @@ static jv find_lib(jq_state *jq, jv lib_name, jv lib_search_path) {
|
||||
jv_free(spath);
|
||||
continue; /* XXX report non-strings in search path?? */
|
||||
}
|
||||
jv testpath = jq_realpath(jv_string_fmt("%s/%s.jq",
|
||||
// Try .../module/last/component.jq
|
||||
jv testpath = jq_realpath(jv_string_fmt("%s/%s%s",
|
||||
jv_string_value(spath),
|
||||
jv_string_value(rel_path)));
|
||||
jv_string_value(rel_path),
|
||||
suffix));
|
||||
ret = stat(jv_string_value(testpath),&st);
|
||||
if (ret == -1 && errno == ENOENT) {
|
||||
jv_free(testpath);
|
||||
testpath = jq_realpath(jv_string_fmt("%s/%s/%s.jq",
|
||||
// Try .../module/last/component/component.jq
|
||||
testpath = jq_realpath(jv_string_fmt("%s/%s/%s%s",
|
||||
jv_string_value(spath),
|
||||
jv_string_value(rel_path),
|
||||
jv_string_value(lib_name)));
|
||||
jv_string_value(bname),
|
||||
suffix));
|
||||
ret = stat(jv_string_value(testpath),&st);
|
||||
}
|
||||
if (ret == 0) {
|
||||
jv_free(spath);
|
||||
jv_free(rel_path);
|
||||
jv_free(lib_name);
|
||||
jv_free(lib_search_paths);
|
||||
jv_free(search);
|
||||
jv_free(bname);
|
||||
jv_free(spath);
|
||||
return testpath;
|
||||
}
|
||||
jv_free(testpath);
|
||||
jv_free(spath);
|
||||
}
|
||||
jv output = jv_invalid_with_msg(jv_string_fmt("module not found: %s", jv_string_value(lib_name)));
|
||||
jv output = jv_invalid_with_msg(jv_string_fmt("module not found: %s", jv_string_value(rel_path)));
|
||||
jv_free(rel_path);
|
||||
jv_free(lib_name);
|
||||
jv_free(lib_search_paths);
|
||||
jv_free(search);
|
||||
jv_free(bname);
|
||||
return output;
|
||||
}
|
||||
|
||||
static int version_matches(jq_state *jq, block importer, block module) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static jv default_search(jv value) {
|
||||
static jv default_search(jq_state *jq, jv value) {
|
||||
if (!jv_is_valid(value)) {
|
||||
// dependent didn't say; prepend . to system search path listj
|
||||
jv_free(value);
|
||||
return JV_ARRAY(jv_string("."), jv_string("$ORIGIN"));
|
||||
return jv_array_concat(JV_ARRAY(jv_string(".")), jq_get_lib_dirs(jq));
|
||||
}
|
||||
if (jv_get_kind(value) != JV_KIND_ARRAY)
|
||||
return JV_ARRAY(value);
|
||||
@@ -139,38 +189,23 @@ static int process_dependencies(jq_state *jq, jv jq_origin, jv lib_origin, block
|
||||
int nerrors = 0;
|
||||
|
||||
jv_array_foreach(deps, i, dep) {
|
||||
jv name = jv_object_get(jv_copy(dep), jv_string("name"));
|
||||
int is_data = jv_get_kind(jv_object_get(jv_copy(dep), jv_string("is_data"))) == JV_KIND_TRUE;
|
||||
int raw = 0;
|
||||
jv v = jv_object_get(jv_copy(dep), jv_string("raw"));
|
||||
if (jv_get_kind(v) == JV_KIND_TRUE)
|
||||
raw = 1;
|
||||
jv_free(v);
|
||||
jv relpath = validate_relpath(jv_object_get(jv_copy(dep), jv_string("relpath")));
|
||||
jv as = jv_object_get(jv_copy(dep), jv_string("as"));
|
||||
if (!jv_is_valid(as)) {
|
||||
jv_free(as);
|
||||
as = jv_string("");
|
||||
}
|
||||
jv search = default_search(jv_object_get(dep, jv_string("search")));
|
||||
jv_array_foreach(search, k, search_elt) {
|
||||
if (strcmp(".",jv_string_value(search_elt)) == 0) {
|
||||
jv tsearch = jv_copy(lib_origin);
|
||||
jv_free(search_elt);
|
||||
search = jv_array_set(search, k, tsearch);
|
||||
} else if (strncmp("./",jv_string_value(search_elt),sizeof("./")-1) == 0) {
|
||||
jv tsearch = jv_string_fmt("%s/%s",
|
||||
jv_string_value(lib_origin),
|
||||
jv_string_value(search_elt) + sizeof ("./") - 1);
|
||||
jv_free(search_elt);
|
||||
search = jv_array_set(search, k, tsearch);
|
||||
} else if (strncmp("$ORIGIN/",jv_string_value(search_elt),sizeof("$ORIGIN/")-1) == 0) {
|
||||
jv tsearch = jv_string_fmt("%s/%s",
|
||||
jv_string_value(jq_origin),
|
||||
jv_string_value(search_elt) + sizeof ("$ORIGIN/") - 1);
|
||||
jv_free(search_elt);
|
||||
search = jv_array_set(search, k, tsearch);
|
||||
} else {
|
||||
jv_free(search_elt);
|
||||
}
|
||||
}
|
||||
jv lib_path = find_lib(jq, name, search);
|
||||
assert(jv_is_valid(as) && jv_get_kind(as) == JV_KIND_STRING);
|
||||
jv search = default_search(jq, jv_object_get(dep, jv_string("search")));
|
||||
// dep is now freed; do not reuse
|
||||
|
||||
// find_lib does a lot of work that could be cached...
|
||||
jv resolved = find_lib(jq, relpath, search, is_data ? ".json" : ".jq", jv_copy(jq_origin), jv_copy(lib_origin));
|
||||
// XXX ...move the rest of this into a callback.
|
||||
if (!jv_is_valid(lib_path)) {
|
||||
jv emsg = jv_invalid_get_msg(lib_path);
|
||||
if (!jv_is_valid(resolved)) {
|
||||
jv emsg = jv_invalid_get_msg(resolved);
|
||||
jq_report_error(jq, jv_string_fmt("jq: error: %s\n",jv_string_value(emsg)));
|
||||
jv_free(emsg);
|
||||
jv_free(as);
|
||||
@@ -181,26 +216,20 @@ static int process_dependencies(jq_state *jq, jv jq_origin, jv lib_origin, block
|
||||
}
|
||||
uint64_t state_idx = 0;
|
||||
for (; state_idx < lib_state->ct; ++state_idx) {
|
||||
if (strcmp(lib_state->names[state_idx],jv_string_value(lib_path)) == 0)
|
||||
if (strcmp(lib_state->names[state_idx],jv_string_value(resolved)) == 0)
|
||||
break;
|
||||
}
|
||||
if (state_idx < lib_state->ct) { // Found
|
||||
// XXX Check version matching here!
|
||||
if (version_matches(jq, bk, lib_state->defs[state_idx]))
|
||||
bk = block_bind_library(lib_state->defs[state_idx], bk, OP_IS_CALL_PSEUDO, jv_string_value(as));
|
||||
else
|
||||
// XXX Would be nice to have the dependent's name here too
|
||||
jq_report_error(jq, jv_string_fmt("jq: error: version mismatch for %s", jv_string_value(name)));
|
||||
jv_free(lib_path);
|
||||
jv_free(resolved);
|
||||
// Bind the library to the program
|
||||
bk = block_bind_library(lib_state->defs[state_idx], bk, OP_IS_CALL_PSEUDO, jv_string_value(as));
|
||||
} else { // Not found. Add it to the table before binding.
|
||||
block dep_def_block = gen_noop();
|
||||
nerrors += load_library(jq, lib_path, &dep_def_block, lib_state);
|
||||
nerrors += load_library(jq, resolved, is_data, raw, jv_string_value(as), &dep_def_block, lib_state);
|
||||
// resolved has been freed
|
||||
if (nerrors == 0) {
|
||||
// XXX Check version matching here!
|
||||
if (version_matches(jq, bk, dep_def_block))
|
||||
bk = block_bind_library(dep_def_block, bk, OP_IS_CALL_PSEUDO, jv_string_value(as));
|
||||
else
|
||||
jq_report_error(jq, jv_string_fmt("jq: error: version mismatch for %s", jv_string_value(name)));
|
||||
// Bind the library to the program
|
||||
bk = block_bind_library(dep_def_block, bk, OP_IS_CALL_PSEUDO, jv_string_value(as));
|
||||
}
|
||||
}
|
||||
jv_free(as);
|
||||
@@ -213,30 +242,48 @@ static int process_dependencies(jq_state *jq, jv jq_origin, jv lib_origin, block
|
||||
|
||||
// Loads the library at lib_path into lib_state, putting the library's defs
|
||||
// into *out_block
|
||||
static int load_library(jq_state *jq, jv lib_path, block *out_block, struct lib_loading_state *lib_state) {
|
||||
static int load_library(jq_state *jq, jv lib_path, int is_data, int raw, const char *as, block *out_block, struct lib_loading_state *lib_state) {
|
||||
int nerrors = 0;
|
||||
struct locfile* src;
|
||||
struct locfile* src = NULL;
|
||||
block program;
|
||||
jv data = jv_load_file(jv_string_value(lib_path), 1);
|
||||
jv data;
|
||||
if (is_data && !raw)
|
||||
data = jv_load_file(jv_string_value(lib_path), 0);
|
||||
else
|
||||
data = jv_load_file(jv_string_value(lib_path), 1);
|
||||
int state_idx;
|
||||
if (jv_is_valid(data)) {
|
||||
if (!jv_is_valid(data)) {
|
||||
if (jv_invalid_has_msg(jv_copy(data)))
|
||||
data = jv_invalid_get_msg(data);
|
||||
else
|
||||
data = jv_string("unknown error");
|
||||
jq_report_error(jq, jv_string_fmt("jq: error loading data file %s: %s\n", jv_string_value(lib_path), jv_string_value(data)));
|
||||
nerrors++;
|
||||
goto out;
|
||||
} else if (is_data) {
|
||||
// import "foo" as $bar;
|
||||
program = gen_const_global(jv_copy(data), as);
|
||||
} else {
|
||||
// import "foo" as bar;
|
||||
src = locfile_init(jq, jv_string_value(data), jv_string_length_bytes(jv_copy(data)));
|
||||
nerrors += jq_parse_library(src, &program);
|
||||
if (nerrors == 0) {
|
||||
state_idx = lib_state->ct++;
|
||||
lib_state->names = realloc(lib_state->names, lib_state->ct * sizeof(const char *));
|
||||
lib_state->defs = realloc(lib_state->defs, lib_state->ct * sizeof(block));
|
||||
lib_state->names[state_idx] = strdup(jv_string_value(lib_path));
|
||||
lib_state->defs[state_idx] = program;
|
||||
char *lib_origin = strdup(jv_string_value(lib_path));
|
||||
nerrors += process_dependencies(jq, jq_get_jq_origin(jq),
|
||||
jv_string(dirname(lib_origin)),
|
||||
&lib_state->defs[state_idx], lib_state);
|
||||
&program, lib_state);
|
||||
free(lib_origin);
|
||||
*out_block = lib_state->defs[state_idx];
|
||||
}
|
||||
locfile_free(src);
|
||||
}
|
||||
state_idx = lib_state->ct++;
|
||||
lib_state->names = realloc(lib_state->names, lib_state->ct * sizeof(const char *));
|
||||
lib_state->defs = realloc(lib_state->defs, lib_state->ct * sizeof(block));
|
||||
lib_state->names[state_idx] = strdup(jv_string_value(lib_path));
|
||||
lib_state->defs[state_idx] = program;
|
||||
*out_block = program;
|
||||
if (src)
|
||||
locfile_free(src);
|
||||
out:
|
||||
jv_free(lib_path);
|
||||
jv_free(data);
|
||||
return nerrors;
|
||||
@@ -244,8 +291,11 @@ static int load_library(jq_state *jq, jv lib_path, block *out_block, struct lib_
|
||||
|
||||
// FIXME It'd be nice to have an option to search the same search path
|
||||
// as we do in process_dependencies.
|
||||
jv load_module_meta(jq_state *jq, jv modname) {
|
||||
jv lib_path = find_lib(jq, modname, jv_array());
|
||||
jv load_module_meta(jq_state *jq, jv mod_relpath) {
|
||||
// We can't know the caller's origin; we could though, if it was passed in
|
||||
jv lib_path = find_lib(jq, validate_relpath(mod_relpath), jq_get_lib_dirs(jq), ".jq", jq_get_jq_origin(jq), jv_null());
|
||||
if (!jv_is_valid(lib_path))
|
||||
return lib_path;
|
||||
jv meta = jv_null();
|
||||
jv data = jv_load_file(jv_string_value(lib_path), 1);
|
||||
if (jv_is_valid(data)) {
|
||||
@@ -278,7 +328,7 @@ int load_program(jq_state *jq, struct locfile* src, block *out_block) {
|
||||
block libs = gen_noop();
|
||||
for (uint64_t i = 0; i < lib_state.ct; ++i) {
|
||||
free(lib_state.names[i]);
|
||||
if (nerrors == 0)
|
||||
if (nerrors == 0 && !block_is_const(lib_state.defs[i]))
|
||||
libs = block_join(libs, lib_state.defs[i]);
|
||||
else
|
||||
block_free(lib_state.defs[i]);
|
||||
|
||||
Reference in new issue
Block a user