rubydex 0.2.8 → 0.3.0
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.
- checksums.yaml +4 -4
- data/README.md +69 -3
- data/THIRD_PARTY_LICENSES.html +168 -1381
- data/exe/rdx +136 -34
- data/ext/rubydex/declaration.c +1 -1
- data/ext/rubydex/definition.c +32 -4
- data/ext/rubydex/extconf.rb +0 -8
- data/ext/rubydex/graph.c +29 -18
- data/ext/rubydex/query.c +105 -0
- data/ext/rubydex/query.h +8 -0
- data/ext/rubydex/reference.c +60 -0
- data/ext/rubydex/rubydex.c +2 -0
- data/ext/rubydex/utils.c +12 -0
- data/ext/rubydex/utils.h +5 -0
- data/lib/rubydex/mcp_server/protocol.rb +156 -0
- data/lib/rubydex/mcp_server/tools/base_tool.rb +109 -0
- data/lib/rubydex/mcp_server/tools/codebase_stats_tool.rb +30 -0
- data/lib/rubydex/mcp_server/tools/find_constant_references_tool.rb +46 -0
- data/lib/rubydex/mcp_server/tools/get_declaration_tool.rb +68 -0
- data/lib/rubydex/mcp_server/tools/get_descendants_tool.rb +46 -0
- data/lib/rubydex/mcp_server/tools/get_file_declarations_tool.rb +55 -0
- data/lib/rubydex/mcp_server/tools/search_declarations_tool.rb +55 -0
- data/lib/rubydex/mcp_server.rb +219 -0
- data/lib/rubydex/version.rb +1 -1
- data/rbi/rubydex.rbi +26 -4
- data/rust/Cargo.lock +5 -552
- data/rust/Cargo.toml +0 -1
- data/rust/rubydex/Cargo.toml +1 -0
- data/rust/rubydex/benches/graph_memory.rs +22 -4
- data/rust/rubydex/src/compile_assertions.rs +15 -0
- data/rust/rubydex/src/config.rs +54 -34
- data/rust/rubydex/src/diagnostic.rs +1 -1
- data/rust/rubydex/src/indexing/rbs_indexer.rs +14 -2
- data/rust/rubydex/src/indexing/ruby_indexer.rs +49 -58
- data/rust/rubydex/src/indexing/ruby_indexer_tests.rs +72 -16
- data/rust/rubydex/src/listing.rs +159 -102
- data/rust/rubydex/src/main.rs +3 -125
- data/rust/rubydex/src/model/declaration.rs +0 -11
- data/rust/rubydex/src/model/definitions.rs +27 -26
- data/rust/rubydex/src/model/document.rs +43 -7
- data/rust/rubydex/src/model/graph.rs +47 -35
- data/rust/rubydex/src/model/id.rs +55 -0
- data/rust/rubydex/src/model/ids.rs +21 -9
- data/rust/rubydex/src/model/name.rs +35 -7
- data/rust/rubydex/src/model/references.rs +16 -13
- data/rust/rubydex/src/operation/applier.rs +0 -2
- data/rust/rubydex/src/operation/ruby_builder.rs +48 -59
- data/rust/rubydex/src/query/cypher/schema.rs +790 -0
- data/rust/rubydex/src/query/cypher/schema_info.rs +161 -0
- data/rust/rubydex/src/query/cypher/tests.rs +228 -0
- data/rust/rubydex/src/query/cypher.rs +57 -0
- data/rust/rubydex/src/query.rs +2 -0
- data/rust/rubydex/src/resolution.rs +248 -227
- data/rust/rubydex/src/resolution_tests.rs +263 -65
- data/rust/rubydex-sys/src/declaration_api.rs +6 -3
- data/rust/rubydex-sys/src/definition_api.rs +27 -7
- data/rust/rubydex-sys/src/graph_api.rs +175 -27
- data/rust/rubydex-sys/src/reference_api.rs +58 -12
- metadata +17 -10
- data/exe/rubydex_mcp +0 -17
- data/lib/rubydex/bin/rubydex_mcp.exe +0 -0
- data/rust/rubydex-mcp/Cargo.toml +0 -34
- data/rust/rubydex-mcp/src/main.rs +0 -48
- data/rust/rubydex-mcp/src/server.rs +0 -1148
- data/rust/rubydex-mcp/src/tools.rs +0 -49
- data/rust/rubydex-mcp/tests/mcp.rs +0 -302
data/exe/rdx
CHANGED
|
@@ -5,49 +5,151 @@ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
|
|
5
5
|
|
|
6
6
|
require "optparse"
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
USAGE = <<~TEXT
|
|
9
|
+
Usage: rdx <command> [options]
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
Commands:
|
|
12
|
+
query <CYPHER> Run a Cypher query against the workspace graph and print the result.
|
|
13
|
+
Use `query --schema` to describe the queryable schema (labels,
|
|
14
|
+
relationships, properties) without indexing the workspace.
|
|
15
|
+
console Open an interactive session with a populated graph for the current workspace
|
|
16
|
+
mcp [PATH] Run the MCP server for AI assistants (workspace defaults to the current dir)
|
|
17
|
+
help Show this help message
|
|
18
|
+
|
|
19
|
+
Run `rdx <command> --help` for command-specific options.
|
|
20
|
+
TEXT
|
|
21
|
+
|
|
22
|
+
def abort_with_usage(message)
|
|
23
|
+
warn(message)
|
|
24
|
+
warn("")
|
|
25
|
+
warn(USAGE)
|
|
26
|
+
exit(1)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Top-level --version / --help / bare invocation, handled before command dispatch. Each branch
|
|
30
|
+
# performs its action and reports whether it handled the invocation, so we exit exactly once here
|
|
31
|
+
# rather than from inside every branch.
|
|
32
|
+
handled =
|
|
33
|
+
case ARGV.first
|
|
34
|
+
when "--version", "version"
|
|
12
35
|
require "rubydex/version"
|
|
13
36
|
puts "v#{Rubydex::VERSION}"
|
|
14
|
-
|
|
37
|
+
true
|
|
38
|
+
when nil, "-h", "--help", "help"
|
|
39
|
+
puts USAGE
|
|
40
|
+
true
|
|
41
|
+
else
|
|
42
|
+
false
|
|
15
43
|
end
|
|
16
44
|
|
|
17
|
-
|
|
18
|
-
puts parser
|
|
19
|
-
exit
|
|
20
|
-
end
|
|
45
|
+
exit if handled
|
|
21
46
|
|
|
22
|
-
|
|
23
|
-
options[:interactive] = true
|
|
24
|
-
end
|
|
25
|
-
end.parse!
|
|
26
|
-
|
|
27
|
-
require "rubydex"
|
|
47
|
+
command = ARGV.shift
|
|
28
48
|
|
|
29
|
-
def
|
|
30
|
-
print(message)
|
|
49
|
+
def with_timer(io, message)
|
|
50
|
+
io.print(message)
|
|
31
51
|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
|
32
|
-
|
|
52
|
+
yield
|
|
33
53
|
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start
|
|
34
|
-
puts
|
|
54
|
+
io.puts(" finished in #{duration.round(2)}ms")
|
|
35
55
|
end
|
|
36
56
|
|
|
37
|
-
graph
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
begin
|
|
45
|
-
require "irb"
|
|
46
|
-
IRB.setup(nil)
|
|
47
|
-
IRB.conf[:IRB_NAME] = "rubydex"
|
|
48
|
-
workspace = IRB::WorkSpace.new(binding)
|
|
49
|
-
IRB::Irb.new(workspace).run(IRB.conf)
|
|
50
|
-
rescue LoadError
|
|
51
|
-
abort("Interactive mode requires `irb` to be in the bundle")
|
|
52
|
-
end
|
|
57
|
+
# Builds the workspace graph, sending progress messages to `progress_io`.
|
|
58
|
+
def build_graph(progress_io)
|
|
59
|
+
graph = Rubydex::Graph.new
|
|
60
|
+
graph.load_config
|
|
61
|
+
with_timer(progress_io, "Indexing workspace...") { graph.index_workspace }
|
|
62
|
+
with_timer(progress_io, "Resolving graph...") { graph.resolve }
|
|
63
|
+
graph
|
|
53
64
|
end
|
|
65
|
+
|
|
66
|
+
require "rubydex"
|
|
67
|
+
|
|
68
|
+
# Resolve the command into an operation on a populated graph. Each command parses its own options
|
|
69
|
+
# and does any graph-independent work here; a command that needs no graph (like `query --schema`)
|
|
70
|
+
# handles itself and exits. Whatever falls through returns a lambda that runs against the graph.
|
|
71
|
+
operation =
|
|
72
|
+
case command
|
|
73
|
+
when "query"
|
|
74
|
+
schema = false
|
|
75
|
+
format = "table"
|
|
76
|
+
OptionParser.new do |parser|
|
|
77
|
+
parser.banner = "Usage: rdx query <CYPHER> [options]"
|
|
78
|
+
parser.on("--schema", "Describe the queryable schema instead of running a query") { schema = true }
|
|
79
|
+
parser.on("--format FORMAT", ["table", "json"], "Output format (table or json)") { |value| format = value }
|
|
80
|
+
parser.on("-h", "--help", "Show this help") do
|
|
81
|
+
puts parser
|
|
82
|
+
exit
|
|
83
|
+
end
|
|
84
|
+
end.parse!
|
|
85
|
+
|
|
86
|
+
query = ARGV.shift
|
|
87
|
+
|
|
88
|
+
if schema
|
|
89
|
+
warn("warning: ignoring query argument because `--schema` was given") if query
|
|
90
|
+
# The schema is static, so describe it without building a graph, then exit.
|
|
91
|
+
print(Rubydex::Query.schema(format))
|
|
92
|
+
exit
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
abort_with_usage("`query` requires a Cypher query argument (or pass `--schema`)") if query.nil? || query.empty?
|
|
96
|
+
|
|
97
|
+
# Parse the query up front so a malformed query fails fast, before the expensive indexing below.
|
|
98
|
+
parsed = begin
|
|
99
|
+
Rubydex::Query.parse(query)
|
|
100
|
+
rescue ArgumentError => e
|
|
101
|
+
abort(e.message)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
lambda do |graph|
|
|
105
|
+
print(parsed.render(graph, format))
|
|
106
|
+
rescue ArgumentError => e
|
|
107
|
+
abort(e.message)
|
|
108
|
+
end
|
|
109
|
+
when "console"
|
|
110
|
+
OptionParser.new do |parser|
|
|
111
|
+
parser.banner = "Usage: rdx console"
|
|
112
|
+
parser.on("-h", "--help", "Show this help") do
|
|
113
|
+
puts parser
|
|
114
|
+
exit
|
|
115
|
+
end
|
|
116
|
+
end.parse!
|
|
117
|
+
|
|
118
|
+
lambda do |graph|
|
|
119
|
+
require "irb"
|
|
120
|
+
IRB.setup(nil)
|
|
121
|
+
IRB.conf[:IRB_NAME] = "rubydex"
|
|
122
|
+
IRB::Irb.new(IRB::WorkSpace.new(binding)).run(IRB.conf)
|
|
123
|
+
rescue LoadError
|
|
124
|
+
abort("Interactive mode requires `irb` to be in the bundle")
|
|
125
|
+
end
|
|
126
|
+
when "mcp"
|
|
127
|
+
parser = OptionParser.new do |p|
|
|
128
|
+
p.banner = "Usage: rdx mcp [PATH]"
|
|
129
|
+
p.on("-h", "--help", "Show this help") do
|
|
130
|
+
puts p
|
|
131
|
+
exit
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
begin
|
|
135
|
+
parser.parse!
|
|
136
|
+
rescue OptionParser::ParseError => e
|
|
137
|
+
abort_with_usage(e.message)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
path = ARGV.shift || Dir.pwd
|
|
141
|
+
abort_with_usage("unexpected argument: #{ARGV.first}") unless ARGV.empty?
|
|
142
|
+
|
|
143
|
+
# The MCP server manages its own graph lifecycle for the given workspace, so it runs here
|
|
144
|
+
# instead of falling through to the shared graph build below.
|
|
145
|
+
require "rubydex/mcp_server"
|
|
146
|
+
Rubydex::MCPServer.run(path)
|
|
147
|
+
exit
|
|
148
|
+
else
|
|
149
|
+
abort_with_usage("unknown command: #{command}")
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Everything that reaches here operates on a populated graph. Progress goes to stderr so stdout
|
|
153
|
+
# carries only the command's output (e.g. for piping a query's JSON).
|
|
154
|
+
graph = build_graph($stderr)
|
|
155
|
+
operation.call(graph)
|
data/ext/rubydex/declaration.c
CHANGED
|
@@ -236,7 +236,7 @@ static VALUE rdxr_declaration_owner(VALUE self) {
|
|
|
236
236
|
const CDeclaration *decl = rdx_declaration_owner(graph, data->id);
|
|
237
237
|
|
|
238
238
|
if (decl == NULL) {
|
|
239
|
-
rb_raise(rb_eRuntimeError, "
|
|
239
|
+
rb_raise(rb_eRuntimeError, "Declaration must exist for a valid id");
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
VALUE decl_class = rdxi_declaration_class_for_kind(decl->kind);
|
data/ext/rubydex/definition.c
CHANGED
|
@@ -18,6 +18,7 @@ static VALUE mRubydex;
|
|
|
18
18
|
static VALUE cInclude;
|
|
19
19
|
static VALUE cPrepend;
|
|
20
20
|
static VALUE cExtend;
|
|
21
|
+
static VALUE cDocument;
|
|
21
22
|
VALUE cComment;
|
|
22
23
|
VALUE cDefinition;
|
|
23
24
|
VALUE cClassDefinition;
|
|
@@ -88,6 +89,9 @@ static VALUE rdxr_definition_location(VALUE self) {
|
|
|
88
89
|
void *graph = rdxi_graph_from_handle(self, &data);
|
|
89
90
|
|
|
90
91
|
Location *loc = rdx_definition_location(graph, data->id);
|
|
92
|
+
if (loc == NULL) {
|
|
93
|
+
rb_raise(rb_eRuntimeError, "Definition must exist for a valid id");
|
|
94
|
+
}
|
|
91
95
|
VALUE location = rdxi_build_location_value(loc);
|
|
92
96
|
rdx_location_free(loc);
|
|
93
97
|
|
|
@@ -105,10 +109,11 @@ static VALUE rdxr_definition_comments(VALUE self) {
|
|
|
105
109
|
void *graph = rdxi_graph_from_handle(self, &data);
|
|
106
110
|
|
|
107
111
|
CommentArray *arr = rdx_definition_comments(graph, data->id);
|
|
108
|
-
if (arr == NULL
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
if (arr == NULL) {
|
|
113
|
+
rb_raise(rb_eRuntimeError, "Definition must exist for a valid id");
|
|
114
|
+
}
|
|
115
|
+
if (arr->len == 0) {
|
|
116
|
+
rdx_definition_comments_free(arr);
|
|
112
117
|
return rb_ary_new();
|
|
113
118
|
}
|
|
114
119
|
|
|
@@ -261,6 +266,27 @@ static VALUE rdxr_definition_lexical_nesting(VALUE self) {
|
|
|
261
266
|
return nesting;
|
|
262
267
|
}
|
|
263
268
|
|
|
269
|
+
/*
|
|
270
|
+
* call-seq:
|
|
271
|
+
* document -> Rubydex::Document
|
|
272
|
+
*
|
|
273
|
+
* Returns the document this definition belongs to.
|
|
274
|
+
*/
|
|
275
|
+
static VALUE rdxr_definition_document(VALUE self) {
|
|
276
|
+
HandleData *data;
|
|
277
|
+
void *graph = rdxi_graph_from_handle(self, &data);
|
|
278
|
+
|
|
279
|
+
const uint64_t *uri_id = rdx_definition_document(graph, data->id);
|
|
280
|
+
if (uri_id == NULL) {
|
|
281
|
+
rb_raise(rb_eRuntimeError, "Definition not found");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
VALUE argv[] = {data->graph_obj, ULL2NUM(*uri_id)};
|
|
285
|
+
free_u64(uri_id);
|
|
286
|
+
|
|
287
|
+
return rb_class_new_instance(2, argv, cDocument);
|
|
288
|
+
}
|
|
289
|
+
|
|
264
290
|
static VALUE rdxi_build_constant_reference(VALUE graph_obj, const CConstantReference *cref) {
|
|
265
291
|
VALUE ref_class = (cref->declaration_id == 0)
|
|
266
292
|
? cUnresolvedConstantReference
|
|
@@ -397,6 +423,7 @@ void rdxi_initialize_definition(VALUE mod) {
|
|
|
397
423
|
cInclude = rb_const_get(mRubydex, rb_intern("Include"));
|
|
398
424
|
cPrepend = rb_const_get(mRubydex, rb_intern("Prepend"));
|
|
399
425
|
cExtend = rb_const_get(mRubydex, rb_intern("Extend"));
|
|
426
|
+
cDocument = rb_const_get(mRubydex, rb_intern("Document"));
|
|
400
427
|
|
|
401
428
|
cComment = rb_define_class_under(mRubydex, "Comment", rb_cObject);
|
|
402
429
|
|
|
@@ -412,6 +439,7 @@ void rdxi_initialize_definition(VALUE mod) {
|
|
|
412
439
|
rb_define_method(cDefinition, "declaration", rdxr_definition_declaration, 0);
|
|
413
440
|
rb_define_method(cDefinition, "lexical_owner", rdxr_definition_lexical_owner, 0);
|
|
414
441
|
rb_define_method(cDefinition, "lexical_nesting", rdxr_definition_lexical_nesting, 0);
|
|
442
|
+
rb_define_method(cDefinition, "document", rdxr_definition_document, 0);
|
|
415
443
|
|
|
416
444
|
cClassDefinition = rb_define_class_under(mRubydex, "ClassDefinition", cDefinition);
|
|
417
445
|
rb_define_method(cClassDefinition, "superclass", rdxr_class_definition_superclass, 0);
|
data/ext/rubydex/extconf.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "mkmf"
|
|
4
|
-
require "fileutils"
|
|
5
4
|
require "pathname"
|
|
6
5
|
|
|
7
6
|
unless system("cargo", "--version", out: File::NULL, err: File::NULL)
|
|
@@ -82,12 +81,6 @@ else
|
|
|
82
81
|
end
|
|
83
82
|
|
|
84
83
|
lib_dir = gem_dir.join("lib").join("rubydex")
|
|
85
|
-
mcp_bin_dir = lib_dir.join("bin")
|
|
86
|
-
FileUtils.mkdir_p(mcp_bin_dir)
|
|
87
|
-
|
|
88
|
-
mcp_executable = Gem.win_platform? ? "rubydex_mcp.exe" : "rubydex_mcp"
|
|
89
|
-
mcp_src = target_dir.join(mcp_executable)
|
|
90
|
-
mcp_dst = mcp_bin_dir.join(mcp_executable)
|
|
91
84
|
|
|
92
85
|
copy_dylib_commands = if Gem.win_platform?
|
|
93
86
|
""
|
|
@@ -114,7 +107,6 @@ new_makefile = makefile.gsub("$(OBJS): $(HDRS) $(ruby_headers)", <<~MAKEFILE.cho
|
|
|
114
107
|
.rust_built: $(RUST_SRCS)
|
|
115
108
|
\t#{cargo_command} || (echo "Compiling Rust failed" && exit 1)
|
|
116
109
|
\t$(COPY) #{bindings_path} #{__dir__}
|
|
117
|
-
\t$(COPY) #{mcp_src} #{mcp_dst}
|
|
118
110
|
\ttouch $@
|
|
119
111
|
|
|
120
112
|
compile_rust: .rust_built
|
data/ext/rubydex/graph.c
CHANGED
|
@@ -63,16 +63,25 @@ const rb_data_type_t graph_type = {
|
|
|
63
63
|
},
|
|
64
64
|
.parent = NULL,
|
|
65
65
|
.data = NULL,
|
|
66
|
-
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
66
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE,
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
-
// Custom allocator for the Graph class. Calls into Rust to create a new
|
|
70
|
-
// as
|
|
69
|
+
// Custom allocator for the Graph class. Calls into Rust to create a new Box<RwLock<Graph>> that gets stored
|
|
70
|
+
// internally as an opaque void pointer. The RwLock provides thread-safe concurrent access across Ractors.
|
|
71
71
|
static VALUE rdxr_graph_alloc(VALUE klass) {
|
|
72
72
|
void *graph = rdx_graph_new();
|
|
73
73
|
return TypedData_Wrap_Struct(klass, &graph_type, graph);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// Graph is intentionally non-copyable. A dup/clone of the TypedData wrapper would alias the
|
|
77
|
+
// same underlying Rust allocation (double-free on GC) or, if deep-copied, balloon memory use.
|
|
78
|
+
// Block both paths: `dup` dispatches to `initialize_copy`, `clone` to `initialize_clone`.
|
|
79
|
+
static VALUE rdxr_graph_initialize_copy(VALUE self, VALUE other) {
|
|
80
|
+
(void)self;
|
|
81
|
+
(void)other;
|
|
82
|
+
rb_raise(rb_eRuntimeError, "Rubydex::Graph cannot be duplicated or cloned");
|
|
83
|
+
}
|
|
84
|
+
|
|
76
85
|
/*
|
|
77
86
|
* call-seq:
|
|
78
87
|
* index_all(file_paths) -> Array[String]
|
|
@@ -796,38 +805,38 @@ static VALUE rdxr_graph_complete_method_argument(int argc, VALUE *argv, VALUE se
|
|
|
796
805
|
|
|
797
806
|
/*
|
|
798
807
|
* call-seq:
|
|
799
|
-
*
|
|
808
|
+
* exclude_patterns(patterns) -> nil
|
|
800
809
|
*
|
|
801
|
-
* Excludes the
|
|
810
|
+
* Excludes files matching the given glob patterns from discovery during indexing.
|
|
802
811
|
*/
|
|
803
|
-
static VALUE
|
|
804
|
-
Check_Type(
|
|
805
|
-
rdxi_check_array_of_strings(
|
|
812
|
+
static VALUE rdxr_graph_exclude_patterns(VALUE self, VALUE patterns) {
|
|
813
|
+
Check_Type(patterns, T_ARRAY);
|
|
814
|
+
rdxi_check_array_of_strings(patterns);
|
|
806
815
|
|
|
807
|
-
size_t length = RARRAY_LEN(
|
|
808
|
-
char **
|
|
816
|
+
size_t length = RARRAY_LEN(patterns);
|
|
817
|
+
char **converted_patterns = rdxi_str_array_to_char(patterns, length);
|
|
809
818
|
|
|
810
819
|
void *graph;
|
|
811
820
|
TypedData_Get_Struct(self, void*, &graph_type, graph);
|
|
812
821
|
|
|
813
|
-
|
|
814
|
-
rdxi_free_str_array(
|
|
822
|
+
rdx_graph_exclude_patterns(graph, (const char **)converted_patterns, length);
|
|
823
|
+
rdxi_free_str_array(converted_patterns, length);
|
|
815
824
|
|
|
816
825
|
return Qnil;
|
|
817
826
|
}
|
|
818
827
|
|
|
819
828
|
/*
|
|
820
829
|
* call-seq:
|
|
821
|
-
*
|
|
830
|
+
* excluded_patterns -> Array[String]
|
|
822
831
|
*
|
|
823
|
-
* Returns the
|
|
832
|
+
* Returns the glob patterns currently excluded from file discovery.
|
|
824
833
|
*/
|
|
825
|
-
static VALUE
|
|
834
|
+
static VALUE rdxr_graph_excluded_patterns(VALUE self) {
|
|
826
835
|
void *graph;
|
|
827
836
|
TypedData_Get_Struct(self, void*, &graph_type, graph);
|
|
828
837
|
|
|
829
838
|
size_t out_count = 0;
|
|
830
|
-
const char *const *results =
|
|
839
|
+
const char *const *results = rdx_graph_excluded_patterns(graph, &out_count);
|
|
831
840
|
|
|
832
841
|
if (results == NULL) {
|
|
833
842
|
return rb_ary_new();
|
|
@@ -943,6 +952,8 @@ void rdxi_initialize_graph(VALUE moduleRubydex) {
|
|
|
943
952
|
id_self_receiver = rb_intern("self_receiver");
|
|
944
953
|
|
|
945
954
|
rb_define_alloc_func(cGraph, rdxr_graph_alloc);
|
|
955
|
+
rb_define_method(cGraph, "initialize_copy", rdxr_graph_initialize_copy, 1);
|
|
956
|
+
rb_define_method(cGraph, "initialize_clone", rdxr_graph_initialize_copy, 1);
|
|
946
957
|
rb_define_method(cGraph, "index_all", rdxr_graph_index_all, 1);
|
|
947
958
|
rb_define_method(cGraph, "index_source", rdxr_graph_index_source, 3);
|
|
948
959
|
rb_define_method(cGraph, "document", rdxr_graph_document, 1);
|
|
@@ -965,8 +976,8 @@ void rdxi_initialize_graph(VALUE moduleRubydex) {
|
|
|
965
976
|
rb_define_method(cGraph, "complete_namespace_access", rdxr_graph_complete_namespace_access, -1);
|
|
966
977
|
rb_define_method(cGraph, "complete_method_call", rdxr_graph_complete_method_call, -1);
|
|
967
978
|
rb_define_method(cGraph, "complete_method_argument", rdxr_graph_complete_method_argument, -1);
|
|
968
|
-
rb_define_method(cGraph, "
|
|
969
|
-
rb_define_method(cGraph, "
|
|
979
|
+
rb_define_method(cGraph, "exclude_patterns", rdxr_graph_exclude_patterns, 1);
|
|
980
|
+
rb_define_method(cGraph, "excluded_patterns", rdxr_graph_excluded_patterns, 0);
|
|
970
981
|
rb_define_method(cGraph, "workspace_path", rdxr_graph_workspace_path, 0);
|
|
971
982
|
rb_define_method(cGraph, "workspace_path=", rdxr_graph_set_workspace_path, 1);
|
|
972
983
|
rb_define_method(cGraph, "load_config", rdxr_graph_load_config, -1);
|
data/ext/rubydex/query.c
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#include "query.h"
|
|
2
|
+
#include "graph.h"
|
|
3
|
+
#include "rustbindings.h"
|
|
4
|
+
#include "utils.h"
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* call-seq:
|
|
8
|
+
* Rubydex::Query.schema(format = :table) -> String
|
|
9
|
+
*
|
|
10
|
+
* Returns a description of the queryable Cypher schema. +format+ may be +:table+ (default) or
|
|
11
|
+
* +:json+. The schema is static, so it does not require a graph.
|
|
12
|
+
*/
|
|
13
|
+
static VALUE rdxr_cypher_schema(int argc, VALUE *argv, VALUE self) {
|
|
14
|
+
VALUE format;
|
|
15
|
+
rb_scan_args(argc, argv, "01", &format);
|
|
16
|
+
|
|
17
|
+
const char *output = rdx_cypher_schema(rdxi_symbol_or_string_cstr(format, "table"));
|
|
18
|
+
VALUE result = output == NULL ? rb_utf8_str_new_cstr("") : rb_utf8_str_new_cstr(output);
|
|
19
|
+
if (output != NULL) {
|
|
20
|
+
free_c_string(output);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Free function for Rubydex::Query: releases the parsed query allocated by Rust.
|
|
27
|
+
static void query_free(void *ptr) {
|
|
28
|
+
if (ptr) {
|
|
29
|
+
rdx_cypher_query_free(ptr);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static const rb_data_type_t query_type = {
|
|
34
|
+
.wrap_struct_name = "Rubydex::Query",
|
|
35
|
+
.function = {
|
|
36
|
+
.dmark = NULL,
|
|
37
|
+
.dfree = query_free,
|
|
38
|
+
.dsize = NULL,
|
|
39
|
+
.dcompact = NULL,
|
|
40
|
+
},
|
|
41
|
+
.parent = NULL,
|
|
42
|
+
.data = NULL,
|
|
43
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/*
|
|
47
|
+
* call-seq:
|
|
48
|
+
* Rubydex::Query.parse(query) -> Rubydex::Query
|
|
49
|
+
*
|
|
50
|
+
* Parses a Cypher query into an opaque, reusable object without needing a graph. Raises
|
|
51
|
+
* ArgumentError on a syntax error, so a query can be validated before building a graph.
|
|
52
|
+
*/
|
|
53
|
+
static VALUE rdxr_query_parse(VALUE klass, VALUE query) {
|
|
54
|
+
Check_Type(query, T_STRING);
|
|
55
|
+
|
|
56
|
+
struct CParseResult result = rdx_cypher_parse(StringValueCStr(query));
|
|
57
|
+
if (result.error != NULL) {
|
|
58
|
+
VALUE message = rb_utf8_str_new_cstr(result.error);
|
|
59
|
+
free_c_string(result.error);
|
|
60
|
+
rb_raise(rb_eArgError, "%s", StringValueCStr(message));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return TypedData_Wrap_Struct(klass, &query_type, result.query);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/*
|
|
67
|
+
* call-seq:
|
|
68
|
+
* render(graph, format = :table) -> String
|
|
69
|
+
*
|
|
70
|
+
* Runs this parsed query against +graph+ and returns the formatted output. +format+ may be
|
|
71
|
+
* +:table+ (default) or +:json+. Raises ArgumentError on an execution or format error.
|
|
72
|
+
*/
|
|
73
|
+
static VALUE rdxr_query_render(int argc, VALUE *argv, VALUE self) {
|
|
74
|
+
VALUE graph_obj, format;
|
|
75
|
+
rb_scan_args(argc, argv, "11", &graph_obj, &format);
|
|
76
|
+
|
|
77
|
+
void *query;
|
|
78
|
+
TypedData_Get_Struct(self, void *, &query_type, query);
|
|
79
|
+
|
|
80
|
+
void *graph;
|
|
81
|
+
TypedData_Get_Struct(graph_obj, void *, &graph_type, graph);
|
|
82
|
+
|
|
83
|
+
struct CQueryResult result = rdx_query_run(query, graph, rdxi_symbol_or_string_cstr(format, "table"));
|
|
84
|
+
|
|
85
|
+
if (result.error != NULL) {
|
|
86
|
+
VALUE message = rb_utf8_str_new_cstr(result.error);
|
|
87
|
+
free_c_string(result.error);
|
|
88
|
+
rb_raise(rb_eArgError, "%s", StringValueCStr(message));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
VALUE output = result.output == NULL ? rb_utf8_str_new_cstr("") : rb_utf8_str_new_cstr(result.output);
|
|
92
|
+
if (result.output != NULL) {
|
|
93
|
+
free_c_string(result.output);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return output;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
void rdxi_initialize_query(VALUE mRubydex) {
|
|
100
|
+
VALUE cQuery = rb_define_class_under(mRubydex, "Query", rb_cObject);
|
|
101
|
+
rb_undef_alloc_func(cQuery);
|
|
102
|
+
rb_define_singleton_method(cQuery, "parse", rdxr_query_parse, 1);
|
|
103
|
+
rb_define_singleton_method(cQuery, "schema", rdxr_cypher_schema, -1);
|
|
104
|
+
rb_define_method(cQuery, "render", rdxr_query_render, -1);
|
|
105
|
+
}
|
data/ext/rubydex/query.h
ADDED
data/ext/rubydex/reference.c
CHANGED
|
@@ -17,6 +17,8 @@ VALUE cUnresolvedConstantReference;
|
|
|
17
17
|
VALUE cResolvedConstantReference;
|
|
18
18
|
VALUE cMethodReference;
|
|
19
19
|
|
|
20
|
+
static VALUE cDocument;
|
|
21
|
+
|
|
20
22
|
/*
|
|
21
23
|
* call-seq:
|
|
22
24
|
* name -> String
|
|
@@ -28,6 +30,9 @@ static VALUE rdxr_constant_reference_name(VALUE self) {
|
|
|
28
30
|
void *graph = rdxi_graph_from_handle(self, &data);
|
|
29
31
|
|
|
30
32
|
const char *name = rdx_constant_reference_name(graph, data->id);
|
|
33
|
+
if (name == NULL) {
|
|
34
|
+
rb_raise(rb_eRuntimeError, "Constant reference must exist for a valid id");
|
|
35
|
+
}
|
|
31
36
|
return rdxi_owned_c_string_to_ruby(name);
|
|
32
37
|
}
|
|
33
38
|
|
|
@@ -42,11 +47,35 @@ static VALUE rdxr_constant_reference_location(VALUE self) {
|
|
|
42
47
|
void *graph = rdxi_graph_from_handle(self, &data);
|
|
43
48
|
|
|
44
49
|
Location *loc = rdx_constant_reference_location(graph, data->id);
|
|
50
|
+
if (loc == NULL) {
|
|
51
|
+
rb_raise(rb_eRuntimeError, "Constant reference must exist for a valid id");
|
|
52
|
+
}
|
|
45
53
|
VALUE location = rdxi_build_location_value(loc);
|
|
46
54
|
rdx_location_free(loc);
|
|
47
55
|
return location;
|
|
48
56
|
}
|
|
49
57
|
|
|
58
|
+
/*
|
|
59
|
+
* call-seq:
|
|
60
|
+
* document -> Rubydex::Document
|
|
61
|
+
*
|
|
62
|
+
* Returns the document this constant reference belongs to.
|
|
63
|
+
*/
|
|
64
|
+
static VALUE rdxr_constant_reference_document(VALUE self) {
|
|
65
|
+
HandleData *data;
|
|
66
|
+
void *graph = rdxi_graph_from_handle(self, &data);
|
|
67
|
+
|
|
68
|
+
const uint64_t *uri_id = rdx_constant_reference_document(graph, data->id);
|
|
69
|
+
if (uri_id == NULL) {
|
|
70
|
+
rb_raise(rb_eRuntimeError, "Constant reference not found");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
VALUE argv[] = {data->graph_obj, ULL2NUM(*uri_id)};
|
|
74
|
+
free_u64(uri_id);
|
|
75
|
+
|
|
76
|
+
return rb_class_new_instance(2, argv, cDocument);
|
|
77
|
+
}
|
|
78
|
+
|
|
50
79
|
/*
|
|
51
80
|
* call-seq:
|
|
52
81
|
* name -> String
|
|
@@ -58,6 +87,9 @@ static VALUE rdxr_method_reference_name(VALUE self) {
|
|
|
58
87
|
void *graph = rdxi_graph_from_handle(self, &data);
|
|
59
88
|
|
|
60
89
|
const char *name = rdx_method_reference_name(graph, data->id);
|
|
90
|
+
if (name == NULL) {
|
|
91
|
+
rb_raise(rb_eRuntimeError, "Method reference must exist for a valid id");
|
|
92
|
+
}
|
|
61
93
|
return rdxi_owned_c_string_to_ruby(name);
|
|
62
94
|
}
|
|
63
95
|
|
|
@@ -72,6 +104,9 @@ static VALUE rdxr_method_reference_location(VALUE self) {
|
|
|
72
104
|
void *graph = rdxi_graph_from_handle(self, &data);
|
|
73
105
|
|
|
74
106
|
Location *loc = rdx_method_reference_location(graph, data->id);
|
|
107
|
+
if (loc == NULL) {
|
|
108
|
+
rb_raise(rb_eRuntimeError, "Method reference must exist for a valid id");
|
|
109
|
+
}
|
|
75
110
|
VALUE location = rdxi_build_location_value(loc);
|
|
76
111
|
rdx_location_free(loc);
|
|
77
112
|
return location;
|
|
@@ -100,6 +135,27 @@ static VALUE rdxr_method_reference_receiver(VALUE self) {
|
|
|
100
135
|
return rb_class_new_instance(2, argv, decl_class);
|
|
101
136
|
}
|
|
102
137
|
|
|
138
|
+
/*
|
|
139
|
+
* call-seq:
|
|
140
|
+
* document -> Rubydex::Document
|
|
141
|
+
*
|
|
142
|
+
* Returns the document this method reference belongs to.
|
|
143
|
+
*/
|
|
144
|
+
static VALUE rdxr_method_reference_document(VALUE self) {
|
|
145
|
+
HandleData *data;
|
|
146
|
+
void *graph = rdxi_graph_from_handle(self, &data);
|
|
147
|
+
|
|
148
|
+
const uint64_t *uri_id = rdx_method_reference_document(graph, data->id);
|
|
149
|
+
if (uri_id == NULL) {
|
|
150
|
+
rb_raise(rb_eRuntimeError, "Method reference not found");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
VALUE argv[] = {data->graph_obj, ULL2NUM(*uri_id)};
|
|
154
|
+
free_u64(uri_id);
|
|
155
|
+
|
|
156
|
+
return rb_class_new_instance(2, argv, cDocument);
|
|
157
|
+
}
|
|
158
|
+
|
|
103
159
|
/*
|
|
104
160
|
* call-seq:
|
|
105
161
|
* declaration -> Rubydex::Declaration
|
|
@@ -123,6 +179,8 @@ static VALUE rdxr_resolved_constant_reference_declaration(VALUE self) {
|
|
|
123
179
|
}
|
|
124
180
|
|
|
125
181
|
void rdxi_initialize_reference(VALUE mRubydex) {
|
|
182
|
+
cDocument = rb_const_get(mRubydex, rb_intern("Document"));
|
|
183
|
+
|
|
126
184
|
cReference = rb_define_class_under(mRubydex, "Reference", rb_cObject);
|
|
127
185
|
rb_define_alloc_func(cReference, rdxr_handle_alloc);
|
|
128
186
|
rb_define_method(cReference, "initialize", rdxr_handle_initialize, 2);
|
|
@@ -132,6 +190,7 @@ void rdxi_initialize_reference(VALUE mRubydex) {
|
|
|
132
190
|
rb_define_alloc_func(cConstantReference, rdxr_handle_alloc);
|
|
133
191
|
rb_define_method(cConstantReference, "initialize", rdxr_handle_initialize, 2);
|
|
134
192
|
rb_define_method(cConstantReference, "location", rdxr_constant_reference_location, 0);
|
|
193
|
+
rb_define_method(cConstantReference, "document", rdxr_constant_reference_document, 0);
|
|
135
194
|
rb_funcall(rb_singleton_class(cConstantReference), rb_intern("private"), 1, ID2SYM(rb_intern("new")));
|
|
136
195
|
|
|
137
196
|
cUnresolvedConstantReference = rb_define_class_under(mRubydex, "UnresolvedConstantReference", cConstantReference);
|
|
@@ -148,4 +207,5 @@ void rdxi_initialize_reference(VALUE mRubydex) {
|
|
|
148
207
|
rb_define_method(cMethodReference, "name", rdxr_method_reference_name, 0);
|
|
149
208
|
rb_define_method(cMethodReference, "location", rdxr_method_reference_location, 0);
|
|
150
209
|
rb_define_method(cMethodReference, "receiver", rdxr_method_reference_receiver, 0);
|
|
210
|
+
rb_define_method(cMethodReference, "document", rdxr_method_reference_document, 0);
|
|
151
211
|
}
|
data/ext/rubydex/rubydex.c
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
#include "document.h"
|
|
5
5
|
#include "graph.h"
|
|
6
6
|
#include "location.h"
|
|
7
|
+
#include "query.h"
|
|
7
8
|
#include "reference.h"
|
|
8
9
|
#include "signature.h"
|
|
9
10
|
|
|
@@ -19,6 +20,7 @@ void Init_rubydex(void) {
|
|
|
19
20
|
*/
|
|
20
21
|
mRubydex = rb_define_module("Rubydex");
|
|
21
22
|
rdxi_initialize_graph(mRubydex);
|
|
23
|
+
rdxi_initialize_query(mRubydex);
|
|
22
24
|
rdxi_initialize_declaration(mRubydex);
|
|
23
25
|
rdxi_initialize_document(mRubydex);
|
|
24
26
|
rdxi_initialize_definition(mRubydex);
|
data/ext/rubydex/utils.c
CHANGED
|
@@ -52,6 +52,18 @@ VALUE rdxi_owned_c_string_to_ruby(const char *string) {
|
|
|
52
52
|
return value;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// Coerce an optional String, Symbol, or nil to a C string, defaulting to `default_value` for nil.
|
|
56
|
+
const char *rdxi_symbol_or_string_cstr(VALUE value, const char *default_value) {
|
|
57
|
+
if (NIL_P(value)) {
|
|
58
|
+
return default_value;
|
|
59
|
+
}
|
|
60
|
+
if (RB_TYPE_P(value, T_SYMBOL)) {
|
|
61
|
+
value = rb_sym2str(value);
|
|
62
|
+
}
|
|
63
|
+
Check_Type(value, T_STRING);
|
|
64
|
+
return StringValueCStr(value);
|
|
65
|
+
}
|
|
66
|
+
|
|
55
67
|
// Yield body for iterating over declarations
|
|
56
68
|
VALUE rdxi_declarations_yield(VALUE args) {
|
|
57
69
|
VALUE self = rb_ary_entry(args, 0);
|