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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a856d1a192105ae54c8af21e06b28b8653d982b770eed3d7ad7d64b4a62b56e2
4
- data.tar.gz: e11a83d4c61b8ce16ff9540869cf9d1c4da4664812d20881a0b6455d85875d7a
3
+ metadata.gz: d117740a94a14a7c3f8a6a62f7bb22ae3dbbc450a909de2c9d5c3a9d5eb6fb91
4
+ data.tar.gz: 1850b47222e7d5be2caf3b50221f2facececedff355873f4b752d08a9aabf096
5
5
  SHA512:
6
- metadata.gz: 1e4c5d6c18e49543ff3776f31bcc42cbb6eb8c2709a9e764dcc947bf43f411725e3e32af7089a9f7059d6e502dc3b155dd94842833a81c557586b39390d5a505
7
- data.tar.gz: 8317d7378cd86da7ea32b09b9e505eaa461a8f60d87a443e44ed82eafc02d32992d483224a221b2d0558a2796d428088d84bcc8205a74cb00f3adafe3c4a86f1
6
+ metadata.gz: 37a8dc083b6a7ee31d93ad01e01a1028bf58a0e5cf4ddee3fdb676ac3af606aeab679d6f9e8498865b42797cb159da93fa65479bce432811ead57f1c8827e0d9
7
+ data.tar.gz: a78b4f787e661d21e02f914058b6877ef863de7ed21beef8502f1ab278fa61799da32dd0a3956f54f4c0a7a9d3af95f34e7203819090f4f1189879333f0d4b70
@@ -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 options pattern path filters ...
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 => e
114
- puts "Error: #{e}"
135
+ rescue
136
+ puts "Error: #{$!}"
115
137
  show_help
116
138
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module CqlRuby
4
4
  #
5
- # Printing Cqlruby::Crumb-s.
5
+ # Printing CqlRuby::Crumb-s.
6
6
  #
7
7
  class AbstractPrinter
8
8
  def print(_crumb)
@@ -21,7 +21,7 @@ module CqlRuby
21
21
  end
22
22
 
23
23
  #
24
- # @param crumb [Cqlruby::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 [Cqlruby::Crumb] crumb
74
+ # @param [CqlRuby::Crumb] crumb
75
75
  # @return [String]
76
76
  def decorate_source_line(crumb)
77
77
  source = crumb.source
@@ -3,7 +3,7 @@
3
3
  module CqlRuby
4
4
  class CrumbCollector
5
5
  #
6
- # @param printer [Cqlruby::AbstractPrinter]
6
+ # @param printer [CqlRuby::AbstractPrinter]
7
7
  #
8
8
  def initialize(printer)
9
9
  super()
@@ -20,7 +20,7 @@ end
20
20
  #
21
21
  # Executes search and dumps results into the collector.
22
22
  #
23
- # @param collector [Cqlruby::CrumbCollector]
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 => e
62
- CqlRuby.log "File #{file} cannot be parsed: #{e}"
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, node, ancestors)
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::Crumb = Struct.new(:full_name, :ancestors, :source_reader) do
103
- def line_no
104
- ancestors.last.location.expression.line
105
- end
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
- def line_col_no
108
- ancestors.last.location.expression.column
109
- end
134
+ def line_no
135
+ anchor.location.expression.line
136
+ end
110
137
 
111
- def source
112
- source_reader.source_line(line_no)
113
- end
138
+ def line_col_no
139
+ anchor.location.expression.column
140
+ end
114
141
 
115
- def surrounding_line(offset)
116
- source_reader.source_line(line_no + offset)
117
- end
142
+ def source
143
+ source_reader.source_line(line_no)
144
+ end
118
145
 
119
- def file_name
120
- source_reader.file
121
- end
146
+ def surrounding_line(offset)
147
+ source_reader.source_line(line_no + offset)
148
+ end
122
149
 
123
- def expression_size
124
- ancestors.last.location.expression.size
125
- end
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
- def type
128
- ancestors.last.type
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, node, ancestors)
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 [Cqlruby::FilterReader] filter_reader
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 [Cqlruby::FilterReader] filter_reader
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 NestRule < Struct.new(:type, :name)
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 "Unknown type: #{type}. Allowed: #{ALLOWED_TYPE}" unless ALLOWED_TYPE.include?(type)
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
- # @attribute [Parser::AST::Node] allowed_types
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<Cqlruby::NestRule>] nest_under
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
- @nest_under << NestRule.from(value)
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)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cql_ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - itarato