cql_ruby 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CqlRuby
4
+ #
5
+ # Printing Cqlruby::Crumb-s.
6
+ #
7
+ class AbstractPrinter
8
+ def print(_crumb)
9
+ raise NotImplementedError
10
+ end
11
+ end
12
+ end
@@ -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: []