cql_ruby 0.0.6 → 0.0.11
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/bin/cql_ruby +27 -5
- data/lib/cql_ruby/abstract_printer.rb +1 -1
- data/lib/cql_ruby/console_printer.rb +2 -2
- data/lib/cql_ruby/crumb_collector.rb +1 -1
- data/lib/cql_ruby/executor.rb +72 -26
- data/lib/cql_ruby/filter_evaluator.rb +82 -4
- data/lib/cql_ruby/filter_reader.rb +23 -7
- data/lib/cql_ruby/pattern_matcher.rb +5 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d117740a94a14a7c3f8a6a62f7bb22ae3dbbc450a909de2c9d5c3a9d5eb6fb91
|
4
|
+
data.tar.gz: 1850b47222e7d5be2caf3b50221f2facececedff355873f4b752d08a9aabf096
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 37a8dc083b6a7ee31d93ad01e01a1028bf58a0e5cf4ddee3fdb676ac3af606aeab679d6f9e8498865b42797cb159da93fa65479bce432811ead57f1c8827e0d9
|
7
|
+
data.tar.gz: a78b4f787e661d21e02f914058b6877ef863de7ed21beef8502f1ab278fa61799da32dd0a3956f54f4c0a7a9d3af95f34e7203819090f4f1189879333f0d4b70
|
data/bin/cql_ruby
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
require 'parser'
|
3
4
|
require 'cql_ruby'
|
4
5
|
|
5
6
|
def show_help
|
6
7
|
puts <<~HELP
|
7
8
|
|
8
9
|
\tSYNOPSIS
|
9
|
-
\t\tcql_ruby
|
10
|
+
\t\tcql_ruby [--token] pattern path options filters ...
|
11
|
+
\t\tcql_ruby --node type path options filters ...
|
10
12
|
|
11
13
|
\tDESCRIPTION
|
12
14
|
\t\tCQL (Code Query Language) is a semantic search tool for your Ruby source code.
|
@@ -14,8 +16,11 @@ def show_help
|
|
14
16
|
\tFILTERS
|
15
17
|
\t\tParent node type: type:T(,T)* Example: type:def,send,arg
|
16
18
|
\t\tNesting under: nest:T(=NAME) Example: nest:def=save_user nest:class=UserManager
|
19
|
+
\t\tHas child: has:T(=NAME) Example: has:const has:def=valid?
|
17
20
|
|
18
21
|
\tOPTIONS
|
22
|
+
\t\t--include=PATTERN Parses only files whose name matches the pattern.
|
23
|
+
\t\t--exclude=PATTERN Parses only files whose name does not match the pattern.
|
19
24
|
\t\t-lN (N is integer) Add N surrounding line before and after.
|
20
25
|
\t\t-nc (--no-color) No color on output.
|
21
26
|
\t\t-nf (--no-file) No file names.
|
@@ -23,9 +28,13 @@ def show_help
|
|
23
28
|
\t\t-nr (--no-recursion) Non-recursive search.
|
24
29
|
\t\t-v -vv -vvv Debug output levels.
|
25
30
|
|
31
|
+
\tALLOWED NODE TYPES
|
32
|
+
\t\tWhen defining filters only valid AST types can be defined. They are:
|
33
|
+
#{Parser::Meta::NODE_TYPES.to_a.join(' ')}
|
34
|
+
|
26
35
|
\tEXAMPLES
|
27
36
|
\t\tcql_ruby user ./
|
28
|
-
\t\tcql_ruby -ns -nr %user_info ./ type:send,arg nest:block nest:class=r/User/i
|
37
|
+
\t\tcql_ruby -ns -nr %user_info ./ type:send,arg nest:block nest:class=r/User/i has:str=WARNING
|
29
38
|
HELP
|
30
39
|
|
31
40
|
exit
|
@@ -39,6 +48,9 @@ def extract_options
|
|
39
48
|
show_source: true,
|
40
49
|
recursive_search: true,
|
41
50
|
surrounding_lines: 0,
|
51
|
+
include_pattern: nil,
|
52
|
+
exclude_pattern: nil,
|
53
|
+
search_type: :token,
|
42
54
|
}
|
43
55
|
|
44
56
|
ARGV.delete_if do |arg|
|
@@ -58,6 +70,14 @@ def extract_options
|
|
58
70
|
options[:recursive_search] = false
|
59
71
|
elsif arg[0..1] == '-l' && arg[2..].to_i > 0
|
60
72
|
options[:surrounding_lines] = arg[2..].to_i
|
73
|
+
elsif arg.start_with?('--include=')
|
74
|
+
options[:include_pattern] = arg.split('=')[1]
|
75
|
+
elsif arg.start_with?('--exclude=')
|
76
|
+
options[:exclude_pattern] = arg.split('=')[1]
|
77
|
+
elsif arg == '--node'
|
78
|
+
options[:search_type] = :node
|
79
|
+
elsif arg == '--token'
|
80
|
+
options[:search_type] = :token
|
61
81
|
else
|
62
82
|
raise "Unknown arg #{arg}"
|
63
83
|
end
|
@@ -85,7 +105,6 @@ begin
|
|
85
105
|
pattern = ARGV.shift
|
86
106
|
CqlRuby.log "Call pattern: <#{pattern}>" if CqlRuby::Config.debug_level_2?
|
87
107
|
|
88
|
-
# TODO Make path patterns universal.
|
89
108
|
path = ARGV.shift
|
90
109
|
CqlRuby.log "Call path: <#{path}>" if CqlRuby::Config.debug_level_2?
|
91
110
|
|
@@ -109,8 +128,11 @@ begin
|
|
109
128
|
path: path,
|
110
129
|
filters: filters,
|
111
130
|
recursive: options[:recursive_search],
|
131
|
+
include: options[:include_pattern],
|
132
|
+
exclude: options[:exclude_pattern],
|
133
|
+
search_type: options[:search_type],
|
112
134
|
).search_all
|
113
|
-
rescue
|
114
|
-
puts "Error: #{
|
135
|
+
rescue
|
136
|
+
puts "Error: #{$!}"
|
115
137
|
show_help
|
116
138
|
end
|
@@ -21,7 +21,7 @@ module CqlRuby
|
|
21
21
|
end
|
22
22
|
|
23
23
|
#
|
24
|
-
# @param crumb [
|
24
|
+
# @param crumb [CqlRuby::Crumb]
|
25
25
|
#
|
26
26
|
def print(crumb)
|
27
27
|
parts = "##{color(97)}#{@counter}#{decor_reset}"
|
@@ -71,7 +71,7 @@ module CqlRuby
|
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
74
|
-
# @param [
|
74
|
+
# @param [CqlRuby::Crumb] crumb
|
75
75
|
# @return [String]
|
76
76
|
def decorate_source_line(crumb)
|
77
77
|
source = crumb.source
|
data/lib/cql_ruby/executor.rb
CHANGED
@@ -20,7 +20,7 @@ end
|
|
20
20
|
#
|
21
21
|
# Executes search and dumps results into the collector.
|
22
22
|
#
|
23
|
-
# @param collector [
|
23
|
+
# @param collector [CqlRuby::CrumbCollector]
|
24
24
|
# @param pattern [String]
|
25
25
|
# @param path [String]
|
26
26
|
# @param filters [Array<String>]
|
@@ -33,7 +33,10 @@ module CqlRuby
|
|
33
33
|
pattern:,
|
34
34
|
path:,
|
35
35
|
filters: [],
|
36
|
-
recursive: true
|
36
|
+
recursive: true,
|
37
|
+
include: nil,
|
38
|
+
exclude: nil,
|
39
|
+
search_type: :token
|
37
40
|
)
|
38
41
|
@collector = collector
|
39
42
|
@filter_reader = filter_reader
|
@@ -41,10 +44,16 @@ module CqlRuby
|
|
41
44
|
@path = path
|
42
45
|
@filters = filters
|
43
46
|
@recursive = recursive
|
47
|
+
@include = include
|
48
|
+
@exclude = exclude
|
49
|
+
@search_type = search_type
|
44
50
|
end
|
45
51
|
|
46
52
|
def search_all
|
47
53
|
files.flat_map do |file|
|
54
|
+
next if !@exclude.nil? && CqlRuby::PatternMatcher.match?(@exclude, file)
|
55
|
+
next unless @include.nil? || CqlRuby::PatternMatcher.match?(@include, file)
|
56
|
+
|
48
57
|
CqlRuby.log "File check: #{file}" if CqlRuby::Config.debug_level_3?
|
49
58
|
search(file)
|
50
59
|
end
|
@@ -58,17 +67,24 @@ module CqlRuby
|
|
58
67
|
walk(ast, [], source_reader)
|
59
68
|
|
60
69
|
nil
|
61
|
-
rescue
|
62
|
-
CqlRuby.log "File #{file} cannot be parsed
|
70
|
+
rescue
|
71
|
+
CqlRuby.log "File #{file} cannot be parsed"
|
72
|
+
CqlRuby.log "Reason: #{$!}" if CqlRuby::Config.debug_level_1?
|
63
73
|
end
|
64
74
|
|
65
75
|
def walk(node, ancestors, source_reader)
|
66
76
|
if node.is_a?(Parser::AST::Node)
|
77
|
+
if search_for_node?
|
78
|
+
if match?(node.type) && CqlRuby::FilterEvaluator.pass?(filter_reader, ancestors, node)
|
79
|
+
collector.add(CqlRuby::Crumb.new(node, ancestors, source_reader))
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
67
83
|
node.children.flat_map do |child|
|
68
84
|
walk(child, ancestors.dup + [node], source_reader)
|
69
85
|
end
|
70
86
|
else
|
71
|
-
if match?(node) && CqlRuby::FilterEvaluator.pass?(filter_reader,
|
87
|
+
if search_for_token? && match?(node) && CqlRuby::FilterEvaluator.pass?(filter_reader, ancestors, node)
|
72
88
|
collector.add(CqlRuby::Crumb.new(node, ancestors, source_reader))
|
73
89
|
end
|
74
90
|
end
|
@@ -90,6 +106,14 @@ module CqlRuby
|
|
90
106
|
Dir.glob(clean_path)
|
91
107
|
end
|
92
108
|
|
109
|
+
def search_for_token?
|
110
|
+
@search_type == :token
|
111
|
+
end
|
112
|
+
|
113
|
+
def search_for_node?
|
114
|
+
@search_type == :node
|
115
|
+
end
|
116
|
+
|
93
117
|
attr_reader :collector
|
94
118
|
attr_reader :filter_reader
|
95
119
|
attr_reader :pattern
|
@@ -99,33 +123,55 @@ module CqlRuby
|
|
99
123
|
end
|
100
124
|
end
|
101
125
|
|
102
|
-
CqlRuby
|
103
|
-
|
104
|
-
ancestors
|
105
|
-
|
126
|
+
module CqlRuby
|
127
|
+
class Crumb
|
128
|
+
def initialize(node, ancestors, source_reader)
|
129
|
+
@node = node
|
130
|
+
@ancestors = ancestors
|
131
|
+
@source_reader = source_reader
|
132
|
+
end
|
106
133
|
|
107
|
-
|
108
|
-
|
109
|
-
|
134
|
+
def line_no
|
135
|
+
anchor.location.expression.line
|
136
|
+
end
|
110
137
|
|
111
|
-
|
112
|
-
|
113
|
-
|
138
|
+
def line_col_no
|
139
|
+
anchor.location.expression.column
|
140
|
+
end
|
114
141
|
|
115
|
-
|
116
|
-
|
117
|
-
|
142
|
+
def source
|
143
|
+
source_reader.source_line(line_no)
|
144
|
+
end
|
118
145
|
|
119
|
-
|
120
|
-
|
121
|
-
|
146
|
+
def surrounding_line(offset)
|
147
|
+
source_reader.source_line(line_no + offset)
|
148
|
+
end
|
122
149
|
|
123
|
-
|
124
|
-
|
125
|
-
|
150
|
+
def file_name
|
151
|
+
source_reader.file
|
152
|
+
end
|
153
|
+
|
154
|
+
def expression_size
|
155
|
+
anchor.location.expression.size
|
156
|
+
end
|
157
|
+
|
158
|
+
def type
|
159
|
+
anchor.type
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
|
164
|
+
def anchor
|
165
|
+
if node.is_a?(Symbol)
|
166
|
+
ancestors.last
|
167
|
+
else
|
168
|
+
node
|
169
|
+
end
|
170
|
+
end
|
126
171
|
|
127
|
-
|
128
|
-
ancestors
|
172
|
+
attr_reader :node
|
173
|
+
attr_reader :ancestors
|
174
|
+
attr_reader :source_reader
|
129
175
|
end
|
130
176
|
end
|
131
177
|
|
@@ -3,17 +3,18 @@
|
|
3
3
|
module CqlRuby
|
4
4
|
class FilterEvaluator
|
5
5
|
class << self
|
6
|
-
def pass?(filter_reader,
|
6
|
+
def pass?(filter_reader, ancestors, node)
|
7
7
|
[
|
8
8
|
pass_type?(filter_reader, ancestors),
|
9
9
|
pass_nesting?(filter_reader, ancestors),
|
10
|
+
pass_has?(filter_reader, ancestors, node),
|
10
11
|
].all?
|
11
12
|
end
|
12
13
|
|
13
14
|
private
|
14
15
|
|
15
16
|
#
|
16
|
-
# @param [
|
17
|
+
# @param [CqlRuby::FilterReader] filter_reader
|
17
18
|
# @param [Array<Parser::AST::Node>] ancestors
|
18
19
|
#
|
19
20
|
# @return [Boolean]
|
@@ -25,7 +26,7 @@ module CqlRuby
|
|
25
26
|
end
|
26
27
|
|
27
28
|
#
|
28
|
-
# @param [
|
29
|
+
# @param [CqlRuby::FilterReader] filter_reader
|
29
30
|
# @param [Array<Parser::AST::Node>] ancestors
|
30
31
|
#
|
31
32
|
# @return [Boolean]
|
@@ -38,7 +39,6 @@ module CqlRuby
|
|
38
39
|
next false unless ancestor.type.to_s == nest_rule.type
|
39
40
|
next true unless nest_rule.restrict_name?
|
40
41
|
|
41
|
-
# TODO Make a proper matcher class.
|
42
42
|
if %w[class module].include?(nest_rule.type)
|
43
43
|
CqlRuby::PatternMatcher.match?(nest_rule.name, ancestor.children[0].children[1])
|
44
44
|
elsif %[def].include?(nest_rule.type)
|
@@ -49,6 +49,84 @@ module CqlRuby
|
|
49
49
|
end
|
50
50
|
end
|
51
51
|
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# @param [CqlRuby::FilterReader] filter_reader
|
55
|
+
# @param [Array<Parser::AST::Node>] ancestors
|
56
|
+
# @param [Any<Parser::AST::Node, Symbol>] node
|
57
|
+
#
|
58
|
+
def pass_has?(filter_reader, ancestors, node)
|
59
|
+
return true unless filter_reader.restrict_children?
|
60
|
+
|
61
|
+
filter_reader.has_leaves.all? do |has_rule|
|
62
|
+
anchor_node = if node.is_a?(Symbol)
|
63
|
+
# TODO: Expand this to other wrappers (loops, conditions, etc).
|
64
|
+
try_get_class(ancestors) || try_get_module(ancestors) || try_get_def(ancestors)
|
65
|
+
else
|
66
|
+
node
|
67
|
+
end
|
68
|
+
next false unless anchor_node
|
69
|
+
|
70
|
+
has_node_with_name?(anchor_node, has_rule)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# @param [Array<Parser::AST::Node>] ancestors
|
76
|
+
#
|
77
|
+
def try_get_class(ancestors)
|
78
|
+
return nil unless ancestors.size >= 2
|
79
|
+
return nil unless ancestors[-1].type == :const
|
80
|
+
return nil unless ancestors[-2].type == :class
|
81
|
+
|
82
|
+
ancestors[-2].children[2]
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# @param [Array<Parser::AST::Node>] ancestors
|
87
|
+
#
|
88
|
+
def try_get_module(ancestors)
|
89
|
+
return nil unless ancestors.size >= 2
|
90
|
+
return nil unless ancestors[-1].type == :const
|
91
|
+
return nil unless ancestors[-2].type == :module
|
92
|
+
|
93
|
+
ancestors[-2].children[1]
|
94
|
+
end
|
95
|
+
|
96
|
+
#
|
97
|
+
# @param [Array<Parser::AST::Node>] ancestors
|
98
|
+
#
|
99
|
+
def try_get_def(ancestors)
|
100
|
+
return nil unless ancestors.size >= 1
|
101
|
+
return nil unless ancestors[-1].type == :def
|
102
|
+
|
103
|
+
ancestors[-1].children[2]
|
104
|
+
end
|
105
|
+
|
106
|
+
#
|
107
|
+
# @param [Parser::AST::Node] anchor_node
|
108
|
+
# @param [CqlRuby::NodeSpec]
|
109
|
+
#
|
110
|
+
def has_node_with_name?(anchor_node, has_rule)
|
111
|
+
return false unless anchor_node.is_a?(Parser::AST::Node)
|
112
|
+
|
113
|
+
fn_children_with_type = ->(node) { node.children.map { |child| [child, node.type] } }
|
114
|
+
to_visit = fn_children_with_type.call(anchor_node)
|
115
|
+
|
116
|
+
until to_visit.empty?
|
117
|
+
current_node, current_type = to_visit.shift
|
118
|
+
|
119
|
+
if current_node.is_a?(Parser::AST::Node)
|
120
|
+
to_visit += fn_children_with_type.call(current_node)
|
121
|
+
else
|
122
|
+
if current_type == has_rule.type.to_sym && CqlRuby::PatternMatcher.match?(has_rule.name, current_node)
|
123
|
+
return true
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
false
|
129
|
+
end
|
52
130
|
end
|
53
131
|
end
|
54
132
|
end
|
@@ -1,9 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# TODO: Have convenience filters for type:_ such as: isclass, ismodule, isdef ...
|
4
|
+
|
3
5
|
module CqlRuby
|
4
|
-
class
|
6
|
+
class NodeSpec < Struct.new(:type, :name)
|
7
|
+
# Make this non duplicated.
|
5
8
|
NAME_ANY = '*'
|
6
|
-
ALLOWED_TYPE = %w[class module def block].freeze
|
7
9
|
|
8
10
|
class << self
|
9
11
|
#
|
@@ -15,8 +17,7 @@ module CqlRuby
|
|
15
17
|
type, name = raw_value.split('=')
|
16
18
|
name ||= NAME_ANY
|
17
19
|
|
18
|
-
raise "
|
19
|
-
raise "Type #{type} cannot have a name." if %w[block].include?(type) && name != NAME_ANY
|
20
|
+
raise "Type '#{type}' is not recognized. See 'cql_ruby --help' for allowed types." unless Parser::Meta::NODE_TYPES.member?(type.to_sym)
|
20
21
|
|
21
22
|
new(type, name)
|
22
23
|
end
|
@@ -38,16 +39,21 @@ module CqlRuby
|
|
38
39
|
# example: type:def,send
|
39
40
|
#
|
40
41
|
class FilterReader
|
41
|
-
|
42
|
+
NESTING_ALLOWED_TYPES = %w[class module def block].freeze
|
43
|
+
|
44
|
+
# @attribute [Array<Symbol>] allowed_types
|
42
45
|
attr_reader :allowed_types
|
43
|
-
# @attribute [Array<
|
46
|
+
# @attribute [Array<CqlRuby::NodeSpec>] nest_under
|
44
47
|
attr_reader :nest_under
|
48
|
+
# @attribute [Array<CqlRuby::NodeSpec>] has_leaves
|
49
|
+
attr_reader :has_leaves
|
45
50
|
|
46
51
|
def initialize(raw_filters)
|
47
52
|
super()
|
48
53
|
|
49
54
|
@allowed_types = []
|
50
55
|
@nest_under = []
|
56
|
+
@has_leaves = []
|
51
57
|
|
52
58
|
parse_raw_filters(raw_filters)
|
53
59
|
end
|
@@ -60,6 +66,10 @@ module CqlRuby
|
|
60
66
|
!@nest_under.empty?
|
61
67
|
end
|
62
68
|
|
69
|
+
def restrict_children?
|
70
|
+
!@has_leaves.empty?
|
71
|
+
end
|
72
|
+
|
63
73
|
private
|
64
74
|
|
65
75
|
# @param [Array<String>] raw_filters
|
@@ -71,7 +81,13 @@ module CqlRuby
|
|
71
81
|
if %w[type t].include?(name)
|
72
82
|
@allowed_types += value.split(',').map(&:to_sym)
|
73
83
|
elsif %w[nest n].include?(name)
|
74
|
-
|
84
|
+
spec = NodeSpec.from(value)
|
85
|
+
raise "Unknown type for nesting: '#{spec.type}' from '#{raw_filter}'. Allowed: #{NESTING_ALLOWED_TYPES}" unless NESTING_ALLOWED_TYPES.include?(spec.type)
|
86
|
+
raise "Type #{spec.type} cannot have a name." if %w[block].include?(spec.type) && spec.restrict_name?
|
87
|
+
|
88
|
+
@nest_under << spec
|
89
|
+
elsif %w[has h].include?(name)
|
90
|
+
@has_leaves << NodeSpec.from(value)
|
75
91
|
end
|
76
92
|
end
|
77
93
|
|
@@ -1,9 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module CqlRuby
|
3
3
|
module PatternMatcher
|
4
|
+
MATCH_ANY = '*'
|
5
|
+
|
4
6
|
def self.match?(pattern, subject)
|
5
|
-
subject = subject.to_s
|
6
7
|
pattern = pattern.to_s
|
8
|
+
return true if pattern == MATCH_ANY
|
9
|
+
|
10
|
+
subject = subject.to_s
|
7
11
|
|
8
12
|
if regex?(pattern)
|
9
13
|
regex_match?(pattern, subject)
|