cql_ruby 0.0.2
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 +7 -0
- data/lib/cql_ruby.rb +12 -0
- data/lib/cql_ruby/abstract_printer.rb +12 -0
- data/lib/cql_ruby/console_printer.rb +78 -0
- data/lib/cql_ruby/crumb_collector.rb +18 -0
- data/lib/cql_ruby/executor.rb +93 -0
- data/lib/cql_ruby/filter_evaluator.rb +54 -0
- data/lib/cql_ruby/filter_reader.rb +81 -0
- data/lib/cql_ruby/pattern_matcher.rb +51 -0
- metadata +50 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6255a37c5d7ad3bb69be36d10bf4056eb8b40729a5989db72ecdc81e30a2784a
|
4
|
+
data.tar.gz: e60a47023bf3062f83157f1d414dfc8122c58df9a4c6898f6048ac91727accea
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 773f82e313c1ce9ad9a3c7f24d243d7045d7ad731d508ec082a5b8f3dce7a0d13078a9f21a0930bd535b2d3b530d801ee534eabcfa70ab44eada8af01b6adc65
|
7
|
+
data.tar.gz: 9dbca8f1684162d99e81a8729cd1544fae6fc32bb7a327e184fa47b44939892a97940dc11e8880bfe99eac7e9d6793a235bfd0e0cae78b3053bc2ad5bf2c286e
|
data/lib/cql_ruby.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CqlRuby
|
4
|
+
; end
|
5
|
+
|
6
|
+
require 'cql/executor'
|
7
|
+
require 'cql/crumb_collector'
|
8
|
+
require 'cql/abstract_printer'
|
9
|
+
require 'cql/console_printer'
|
10
|
+
require 'cql/filter_reader'
|
11
|
+
require 'cql/filter_evaluator'
|
12
|
+
require 'cql/pattern_matcher'
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CqlRuby
|
4
|
+
#
|
5
|
+
# Prints to console.
|
6
|
+
#
|
7
|
+
class ConsolePrinter < ::CqlRuby::AbstractPrinter
|
8
|
+
attr_writer :color_on
|
9
|
+
attr_writer :file_on
|
10
|
+
attr_writer :source_on
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
super
|
14
|
+
|
15
|
+
@color_on = true
|
16
|
+
@file_on = true
|
17
|
+
@source_on = true
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# @param crumb [Cqlruby::Crumb]
|
22
|
+
#
|
23
|
+
def print(crumb)
|
24
|
+
puts "#{color(94)}#{crumb.file_name}#{decor_reset}:#{color(33)}#{crumb.line_no}#{decor_reset} #{color(93)}#{crumb.type}#{decor_reset}" if @file_on
|
25
|
+
puts decorate_source_line(crumb) if @source_on
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def color(code)
|
31
|
+
if @color_on
|
32
|
+
"\e[#{code}m"
|
33
|
+
else
|
34
|
+
''
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def bold
|
39
|
+
if @color_on
|
40
|
+
"\e[1m"
|
41
|
+
else
|
42
|
+
''
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def decor_reset
|
47
|
+
if @color_on
|
48
|
+
"\e[0m"
|
49
|
+
else
|
50
|
+
''
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# @param [Cqlruby::Crumb] crumb
|
55
|
+
# @return [String]
|
56
|
+
def decorate_source_line(crumb)
|
57
|
+
# TODO add +- line surrounding options
|
58
|
+
source = crumb.source
|
59
|
+
from = crumb.line_col_no
|
60
|
+
to = from + crumb.expression_size
|
61
|
+
|
62
|
+
prefix = source[0..from - 1] || ''
|
63
|
+
subject = source[from..to - 1] || ''
|
64
|
+
suffix = source[to..] || ''
|
65
|
+
|
66
|
+
color(90) +
|
67
|
+
prefix +
|
68
|
+
decor_reset +
|
69
|
+
color(31) +
|
70
|
+
bold +
|
71
|
+
subject +
|
72
|
+
decor_reset +
|
73
|
+
color(90) +
|
74
|
+
suffix +
|
75
|
+
decor_reset
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CqlRuby
|
4
|
+
class CrumbCollector
|
5
|
+
#
|
6
|
+
# @param printer [Cqlruby::AbstractPrinter]
|
7
|
+
#
|
8
|
+
def initialize(printer)
|
9
|
+
super()
|
10
|
+
|
11
|
+
@printer = printer
|
12
|
+
end
|
13
|
+
|
14
|
+
def add(crumb)
|
15
|
+
@printer.print(crumb)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'parser/current'
|
4
|
+
|
5
|
+
#
|
6
|
+
# Executes search and dumps results into the collector.
|
7
|
+
#
|
8
|
+
# @param collector [Cqlruby::CrumbCollector]
|
9
|
+
# @param pattern [String]
|
10
|
+
# @param path [String]
|
11
|
+
# @param filters [Array<String>]
|
12
|
+
#
|
13
|
+
CqlRuby::Executor = Struct.new(:collector, :filter_reader, :pattern, :path, :filters) do
|
14
|
+
def search_all
|
15
|
+
files.flat_map do |file|
|
16
|
+
search(file)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def search(file)
|
23
|
+
ast = Parser::CurrentRuby.parse(File.read(file))
|
24
|
+
source_reader = CqlRuby::SourceReader.new(file)
|
25
|
+
walk(ast, [], source_reader)
|
26
|
+
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def walk(node, ancestors, source_reader)
|
31
|
+
if node.is_a?(Parser::AST::Node)
|
32
|
+
node.children.flat_map do |child|
|
33
|
+
walk(child, ancestors.dup + [node], source_reader)
|
34
|
+
end
|
35
|
+
else
|
36
|
+
if match?(node) && CqlRuby::FilterEvaluator.pass?(filter_reader, node, ancestors)
|
37
|
+
collector.add(CqlRuby::Crumb.new(node, ancestors, source_reader))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def match?(target)
|
45
|
+
CqlRuby::PatternMatcher.match?(pattern, target)
|
46
|
+
end
|
47
|
+
|
48
|
+
def files
|
49
|
+
Dir.glob(path)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
CqlRuby::Crumb = Struct.new(:full_name, :ancestors, :source_reader) do
|
54
|
+
def line_no
|
55
|
+
ancestors.last.location.expression.line
|
56
|
+
end
|
57
|
+
|
58
|
+
def line_col_no
|
59
|
+
ancestors.last.location.expression.column
|
60
|
+
end
|
61
|
+
|
62
|
+
def source
|
63
|
+
source_reader.source_line(line_no)
|
64
|
+
end
|
65
|
+
|
66
|
+
def file_name
|
67
|
+
source_reader.file
|
68
|
+
end
|
69
|
+
|
70
|
+
def expression_size
|
71
|
+
ancestors.last.location.expression.size
|
72
|
+
end
|
73
|
+
|
74
|
+
def type
|
75
|
+
ancestors.last.type
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
CqlRuby::SourceReader = Struct.new(:file) do
|
80
|
+
def initialize(*args)
|
81
|
+
super
|
82
|
+
end
|
83
|
+
|
84
|
+
def source_line(n)
|
85
|
+
lines[n - 1].chop
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def lines
|
91
|
+
@lines ||= IO.readlines(file)
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CqlRuby
|
4
|
+
class FilterEvaluator
|
5
|
+
class << self
|
6
|
+
def pass?(filter_reader, node, ancestors)
|
7
|
+
[
|
8
|
+
pass_type?(filter_reader, ancestors),
|
9
|
+
pass_nesting?(filter_reader, ancestors),
|
10
|
+
].all?
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
#
|
16
|
+
# @param [Cqlruby::FilterReader] filter_reader
|
17
|
+
# @param [Array<Parser::AST::Node>] ancestors
|
18
|
+
#
|
19
|
+
# @return [Boolean]
|
20
|
+
#
|
21
|
+
def pass_type?(filter_reader, ancestors)
|
22
|
+
return true unless filter_reader.restrict_types?
|
23
|
+
|
24
|
+
filter_reader.allowed_types.include?(ancestors.last.type)
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# @param [Cqlruby::FilterReader] filter_reader
|
29
|
+
# @param [Array<Parser::AST::Node>] ancestors
|
30
|
+
#
|
31
|
+
# @return [Boolean]
|
32
|
+
#
|
33
|
+
def pass_nesting?(filter_reader, ancestors)
|
34
|
+
return true unless filter_reader.restrict_nesting?
|
35
|
+
|
36
|
+
filter_reader.nest_under.all? do |nest_rule|
|
37
|
+
ancestors.reverse.any? do |ancestor|
|
38
|
+
next false unless ancestor.type.to_s == nest_rule.type
|
39
|
+
next true unless nest_rule.restrict_name?
|
40
|
+
|
41
|
+
# TODO Make a proper matcher class.
|
42
|
+
if %w[class module].include?(nest_rule.type)
|
43
|
+
CqlRuby::PatternMatcher.match?(nest_rule.name, ancestor.children[0].children[1])
|
44
|
+
elsif %[def].include?(nest_rule.type)
|
45
|
+
CqlRuby::PatternMatcher.match?(nest_rule.name, ancestor.children[0])
|
46
|
+
else
|
47
|
+
raise 'Unknown type.'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CqlRuby
|
4
|
+
class NestRule < Struct.new(:type, :name)
|
5
|
+
NAME_ANY = '*'
|
6
|
+
ALLOWED_TYPE = %w[class module def block].freeze
|
7
|
+
|
8
|
+
class << self
|
9
|
+
#
|
10
|
+
# @param [String] raw_value
|
11
|
+
# Format: TYPE(=NAME|=*)
|
12
|
+
# Accepted types: class, module, def, block
|
13
|
+
#
|
14
|
+
def from(raw_value)
|
15
|
+
type, name = raw_value.split('=')
|
16
|
+
name ||= NAME_ANY
|
17
|
+
|
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
|
+
|
21
|
+
new(type, name)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def restrict_name?
|
26
|
+
name != NAME_ANY
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Reads and provides filters.
|
32
|
+
#
|
33
|
+
# Accepted filters and syntax:
|
34
|
+
#
|
35
|
+
# Type:
|
36
|
+
#
|
37
|
+
# type:[name](,[name])*
|
38
|
+
# example: type:def,send
|
39
|
+
#
|
40
|
+
class FilterReader
|
41
|
+
# @attribute [Parser::AST::Node] allowed_types
|
42
|
+
attr_reader :allowed_types
|
43
|
+
# @attribute [Array<Cqlruby::NestRule>] nest_under
|
44
|
+
attr_reader :nest_under
|
45
|
+
|
46
|
+
def initialize(raw_filters)
|
47
|
+
super()
|
48
|
+
|
49
|
+
@allowed_types = []
|
50
|
+
@nest_under = []
|
51
|
+
|
52
|
+
parse_raw_filters(raw_filters)
|
53
|
+
end
|
54
|
+
|
55
|
+
def restrict_types?
|
56
|
+
!@allowed_types.empty?
|
57
|
+
end
|
58
|
+
|
59
|
+
def restrict_nesting?
|
60
|
+
!@nest_under.empty?
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# @param [Array<String>] raw_filters
|
66
|
+
def parse_raw_filters(raw_filters)
|
67
|
+
raw_filters.each do |raw_filter|
|
68
|
+
name, value = raw_filter.split(':')
|
69
|
+
raise "Unrecognized filter: #{raw_filter}" if name.nil? || value.nil?
|
70
|
+
|
71
|
+
if %w[type t].include?(name)
|
72
|
+
@allowed_types += value.split(',').map(&:to_sym)
|
73
|
+
elsif %w[nest n].include?(name)
|
74
|
+
@nest_under << NestRule.from(value)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module CqlRuby
|
3
|
+
module PatternMatcher
|
4
|
+
def self.match?(pattern, subject)
|
5
|
+
subject = subject.to_s
|
6
|
+
pattern = pattern.to_s
|
7
|
+
|
8
|
+
if regex?(pattern)
|
9
|
+
regex_match?(pattern, subject)
|
10
|
+
elsif partial_string?(pattern)
|
11
|
+
partial_string_match?(pattern, subject)
|
12
|
+
else
|
13
|
+
full_string_match?(pattern, subject)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.regex?(pattern)
|
18
|
+
pattern[0..1] == 'r:'
|
19
|
+
end
|
20
|
+
private_class_method :regex?
|
21
|
+
|
22
|
+
def self.partial_string?(pattern)
|
23
|
+
pattern[0] == '%'
|
24
|
+
end
|
25
|
+
private_class_method :partial_string?
|
26
|
+
|
27
|
+
def self.regex_match?(pattern, subject)
|
28
|
+
pattern = pattern[2..]
|
29
|
+
pattern, *mods = pattern.split('+')
|
30
|
+
|
31
|
+
fops = 0
|
32
|
+
fops |= Regexp::IGNORECASE if mods.include?('i')
|
33
|
+
fops |= Regexp::MULTILINE if mods.include?('m')
|
34
|
+
fops |= Regexp::EXTENDED if mods.include?('x')
|
35
|
+
fops |= Regexp::FIXEDENCODING if mods.include?('f')
|
36
|
+
fops |= Regexp::NOENCODING if mods.include?('n')
|
37
|
+
Regexp.new(pattern, fops).match?(subject)
|
38
|
+
end
|
39
|
+
private_class_method :regex_match?
|
40
|
+
|
41
|
+
def self.full_string_match?(pattern, subject)
|
42
|
+
pattern == subject
|
43
|
+
end
|
44
|
+
private_class_method :full_string_match?
|
45
|
+
|
46
|
+
def self.partial_string_match?(pattern, subject)
|
47
|
+
!subject.index(pattern[1..]).nil?
|
48
|
+
end
|
49
|
+
private_class_method :partial_string_match?
|
50
|
+
end
|
51
|
+
end
|
metadata
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cql_ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- itarato
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-07-05 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email: it.arato@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/cql_ruby.rb
|
20
|
+
- lib/cql_ruby/abstract_printer.rb
|
21
|
+
- lib/cql_ruby/console_printer.rb
|
22
|
+
- lib/cql_ruby/crumb_collector.rb
|
23
|
+
- lib/cql_ruby/executor.rb
|
24
|
+
- lib/cql_ruby/filter_evaluator.rb
|
25
|
+
- lib/cql_ruby/filter_reader.rb
|
26
|
+
- lib/cql_ruby/pattern_matcher.rb
|
27
|
+
homepage: https://github.com/itarato/cql
|
28
|
+
licenses:
|
29
|
+
- GPL-3.0-or-later
|
30
|
+
metadata: {}
|
31
|
+
post_install_message:
|
32
|
+
rdoc_options: []
|
33
|
+
require_paths:
|
34
|
+
- lib
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
requirements: []
|
46
|
+
rubygems_version: 3.0.1
|
47
|
+
signing_key:
|
48
|
+
specification_version: 4
|
49
|
+
summary: Code Query Language for Ruby
|
50
|
+
test_files: []
|