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.
@@ -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: []