cql_ruby 0.0.3 → 0.0.8

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: 5ff996bf0e61aa22dd53f9eaf9768f1d2748ccf56a35bf4fb4cc2d65ed1d1e08
4
- data.tar.gz: fbe683ebe25ab5f2a93a42c1f182ec665852536bf3deca9315ba6bf8084e9b24
3
+ metadata.gz: 17840020ae4f57f60763716ac438b33a68abb4b1add767d5f8e88cfb946439c8
4
+ data.tar.gz: 789aa975ced7c62036da026f7e4bd377c09b1502692ff217fb697641d5478d64
5
5
  SHA512:
6
- metadata.gz: c1f7f6577eb7117f8f58057f880dd3dad25caf528a32251114aae9fb922583dcd2b6407b4ab782a4419dd040b04c47b5869fb4e49d0e5923c4ce55ae97c7f6ab
7
- data.tar.gz: 58aca7b1d4323135126fd78c2b6b21df2cdd3b6ae6bdc06f211e75589349c2d286460bcf46f41211024e6bb0d07f4fe68b8364ad1a562ac9a697bdb3cf1ea16b
6
+ metadata.gz: ee8c4e5ea2cfde9a8837cc1e19734e0051424af9c15fe02fc5db49b11984052b70a4acb6d196b94c38820ea34bccbb8d78ff431e5bef45ac0627a55548fae324
7
+ data.tar.gz: 9e550d59e360717069c3198269bbe815d73aae7cc1d43b9532ed2fc4dee60b8f9decee399051ceedb9b0ae21867e785cce0423170f1f417550b3611c018d4270
@@ -1,26 +1,37 @@
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
- \tcql_ruby options pattern path filters ...
10
+ \t\tcql_ruby options pattern path filters ...
10
11
 
11
12
  \tDESCRIPTION
12
- \tCQL (Code Query Language) is a semantic search tool for your Ruby source code.
13
+ \t\tCQL (Code Query Language) is a semantic search tool for your Ruby source code.
13
14
 
14
15
  \tFILTERS
15
- \t\tParent node type: type:T(,T)*
16
+ \t\tParent node type: type:T(,T)* Example: type:def,send,arg
17
+ \t\tNesting under: nest:T(=NAME) Example: nest:def=save_user nest:class=UserManager
18
+ \t\tHas child: has:T(=NAME) Example: has:const has:def=valid?
16
19
 
17
20
  \tOPTIONS
21
+ \t\t-lN (N is integer) Add N surrounding line before and after.
18
22
  \t\t-nc (--no-color) No color on output.
19
23
  \t\t-nf (--no-file) No file names.
20
24
  \t\t-ns (--no-source) No source code.
25
+ \t\t-nr (--no-recursion) Non-recursive search.
26
+ \t\t-v -vv -vvv Debug output levels.
27
+
28
+ \tALLOWED NODE TYPES
29
+ \t\tWhen defining filters only valid AST types can be defined. They are:
30
+ #{Parser::Meta::NODE_TYPES.to_a.join(' ')}
21
31
 
22
32
  \tEXAMPLES
23
- \tcql_ruby -ns update_user_info ./ type:send,arg
33
+ \t\tcql_ruby user ./
34
+ \t\tcql_ruby -ns -nr %user_info ./ type:send,arg nest:block nest:class=r/User/i has:str=WARNING
24
35
  HELP
25
36
 
26
37
  exit
@@ -32,6 +43,8 @@ def extract_options
32
43
  show_color: true,
33
44
  show_file: true,
34
45
  show_source: true,
46
+ recursive_search: true,
47
+ surrounding_lines: 0,
35
48
  }
36
49
 
37
50
  ARGV.delete_if do |arg|
@@ -42,8 +55,15 @@ def extract_options
42
55
  options[:show_file] = false
43
56
  elsif %w[-ns --no-source].include?(arg)
44
57
  options[:show_source] = false
45
- elsif %w[-h --help]
58
+ elsif %w[-h --help].include?(arg)
46
59
  show_help
60
+ elsif %w[-v -vv -vvv].include?(arg)
61
+ lvl = arg.chars.find_all { |c| c == 'v' }.size
62
+ CqlRuby::Config.debug_level = lvl
63
+ elsif %w[-nr --no-recursive].include?(arg)
64
+ options[:recursive_search] = false
65
+ elsif arg[0..1] == '-l' && arg[2..].to_i > 0
66
+ options[:surrounding_lines] = arg[2..].to_i
47
67
  else
48
68
  raise "Unknown arg #{arg}"
49
69
  end
@@ -64,24 +84,38 @@ end
64
84
 
65
85
  begin
66
86
  options = extract_options
87
+ CqlRuby.log "Call options: #{options}" if CqlRuby::Config.debug_level_2?
67
88
 
68
89
  raise unless ARGV.size >= 2
69
90
 
70
91
  pattern = ARGV.shift
92
+ CqlRuby.log "Call pattern: <#{pattern}>" if CqlRuby::Config.debug_level_2?
93
+
71
94
  # TODO Make path patterns universal.
72
95
  path = ARGV.shift
96
+ CqlRuby.log "Call path: <#{path}>" if CqlRuby::Config.debug_level_2?
73
97
 
74
98
  # Rest must be filters - can sink ARGV now.
75
99
  filters = extract_filters
100
+ CqlRuby.log "Call filters: #{filters}" if CqlRuby::Config.debug_level_2?
101
+
76
102
  filter_reader = CqlRuby::FilterReader.new(filters)
77
103
 
78
104
  printer = CqlRuby::ConsolePrinter.new
79
105
  printer.color_on = options[:show_color]
80
106
  printer.file_on = options[:show_file]
81
107
  printer.source_on = options[:show_source]
108
+ printer.surrounding_lines = options[:surrounding_lines]
82
109
 
83
110
  collector = CqlRuby::CrumbCollector.new(printer)
84
- CqlRuby::Executor.new(collector, filter_reader, pattern, path, filters).search_all
111
+ CqlRuby::Executor.new(
112
+ collector: collector,
113
+ filter_reader: filter_reader,
114
+ pattern: pattern,
115
+ path: path,
116
+ filters: filters,
117
+ recursive: options[:recursive_search],
118
+ ).search_all
85
119
  rescue => e
86
120
  puts "Error: #{e}"
87
121
  show_help
@@ -1,12 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module CqlRuby
4
- ; end
3
+ module CqlRuby;
4
+ def self.log(txt)
5
+ p txt
6
+ end
7
+ end
5
8
 
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'
9
+ require 'cql_ruby/executor'
10
+ require 'cql_ruby/crumb_collector'
11
+ require 'cql_ruby/abstract_printer'
12
+ require 'cql_ruby/console_printer'
13
+ require 'cql_ruby/filter_reader'
14
+ require 'cql_ruby/filter_evaluator'
15
+ require 'cql_ruby/pattern_matcher'
@@ -8,6 +8,7 @@ module CqlRuby
8
8
  attr_writer :color_on
9
9
  attr_writer :file_on
10
10
  attr_writer :source_on
11
+ attr_writer :surrounding_lines
11
12
 
12
13
  def initialize
13
14
  super
@@ -15,14 +16,33 @@ module CqlRuby
15
16
  @color_on = true
16
17
  @file_on = true
17
18
  @source_on = true
19
+ @surrounding_lines = 0
20
+ @counter = 0
18
21
  end
19
22
 
20
23
  #
21
24
  # @param crumb [Cqlruby::Crumb]
22
25
  #
23
26
  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
27
+ parts = "##{color(97)}#{@counter}#{decor_reset}"
28
+ parts += " #{color(94)}#{crumb.file_name}#{decor_reset}:#{color(33)}#{crumb.line_no}#{decor_reset} #{color(93)}#{crumb.type}#{decor_reset}" if @file_on
29
+
30
+ if @source_on && @surrounding_lines.positive?
31
+ parts_visible_len = parts.gsub(/\e\[\d+m/, '').size + 1
32
+ indent = ' ' * parts_visible_len
33
+ (-@surrounding_lines).upto(-1).each { |offs| puts "#{indent}#{crumb.surrounding_line(offs)}" }
34
+ end
35
+
36
+ parts += ' ' + decorate_source_line(crumb) if @source_on
37
+
38
+ puts parts
39
+
40
+ if @source_on && @surrounding_lines.positive?
41
+ 1.upto(@surrounding_lines).each { |offs| puts "#{indent}#{crumb.surrounding_line(offs)}" }
42
+ puts '--'
43
+ end
44
+
45
+ @counter += 1
26
46
  end
27
47
 
28
48
  private
@@ -54,7 +74,6 @@ module CqlRuby
54
74
  # @param [Cqlruby::Crumb] crumb
55
75
  # @return [String]
56
76
  def decorate_source_line(crumb)
57
- # TODO add +- line surrounding options
58
77
  source = crumb.source
59
78
  from = crumb.line_col_no
60
79
  to = from + crumb.expression_size
@@ -63,14 +82,14 @@ module CqlRuby
63
82
  subject = source[from..to - 1] || ''
64
83
  suffix = source[to..] || ''
65
84
 
66
- color(90) +
85
+ color(97) +
67
86
  prefix +
68
87
  decor_reset +
69
88
  color(31) +
70
89
  bold +
71
90
  subject +
72
91
  decor_reset +
73
- color(90) +
92
+ color(97) +
74
93
  suffix +
75
94
  decor_reset
76
95
  end
@@ -1,6 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'parser/current'
4
+ require 'pathname'
5
+
6
+ module CqlRuby
7
+ class Config
8
+ @@debug_level = 0
9
+
10
+ class << self
11
+ def debug_level=(lvl); @@debug_level = lvl; end
12
+ def debug_level; @@debug_level; end
13
+ def debug_level_1?; @@debug_level >= 1; end
14
+ def debug_level_2?; @@debug_level >= 2; end
15
+ def debug_level_3?; @@debug_level >= 3; end
16
+ end
17
+ end
18
+ end
4
19
 
5
20
  #
6
21
  # Executes search and dumps results into the collector.
@@ -10,43 +25,77 @@ require 'parser/current'
10
25
  # @param path [String]
11
26
  # @param filters [Array<String>]
12
27
  #
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)
28
+ module CqlRuby
29
+ class Executor
30
+ def initialize(
31
+ collector:,
32
+ filter_reader:,
33
+ pattern:,
34
+ path:,
35
+ filters: [],
36
+ recursive: true
37
+ )
38
+ @collector = collector
39
+ @filter_reader = filter_reader
40
+ @pattern = pattern
41
+ @path = path
42
+ @filters = filters
43
+ @recursive = recursive
17
44
  end
18
- end
19
45
 
20
- private
46
+ def search_all
47
+ files.flat_map do |file|
48
+ CqlRuby.log "File check: #{file}" if CqlRuby::Config.debug_level_3?
49
+ search(file)
50
+ end
51
+ end
21
52
 
22
- def search(file)
23
- ast = Parser::CurrentRuby.parse(File.read(file))
24
- source_reader = CqlRuby::SourceReader.new(file)
25
- walk(ast, [], source_reader)
53
+ private
26
54
 
27
- nil
28
- end
55
+ def search(file)
56
+ ast = Parser::CurrentRuby.parse(File.read(file))
57
+ source_reader = CqlRuby::SourceReader.new(file)
58
+ walk(ast, [], source_reader)
29
59
 
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))
60
+ nil
61
+ rescue => e
62
+ CqlRuby.log "File #{file} cannot be parsed: #{e}"
63
+ end
64
+
65
+ def walk(node, ancestors, source_reader)
66
+ if node.is_a?(Parser::AST::Node)
67
+ node.children.flat_map do |child|
68
+ walk(child, ancestors.dup + [node], source_reader)
69
+ end
70
+ else
71
+ if match?(node) && CqlRuby::FilterEvaluator.pass?(filter_reader, node, ancestors)
72
+ collector.add(CqlRuby::Crumb.new(node, ancestors, source_reader))
73
+ end
38
74
  end
75
+
76
+ nil
39
77
  end
40
78
 
41
- nil
42
- end
79
+ def match?(target)
80
+ CqlRuby::PatternMatcher.match?(pattern, target)
81
+ end
43
82
 
44
- def match?(target)
45
- CqlRuby::PatternMatcher.match?(pattern, target)
46
- end
83
+ def files
84
+ return [path] if File.file?(path)
85
+
86
+ clean_path = Pathname(path).cleanpath.to_s
87
+ clean_path += '/**' if recursive
88
+ clean_path += '/*.rb'
47
89
 
48
- def files
49
- Dir.glob(path)
90
+ Dir.glob(clean_path)
91
+ end
92
+
93
+ attr_reader :collector
94
+ attr_reader :filter_reader
95
+ attr_reader :pattern
96
+ attr_reader :path
97
+ attr_reader :filters
98
+ attr_reader :recursive
50
99
  end
51
100
  end
52
101
 
@@ -63,6 +112,10 @@ CqlRuby::Crumb = Struct.new(:full_name, :ancestors, :source_reader) do
63
112
  source_reader.source_line(line_no)
64
113
  end
65
114
 
115
+ def surrounding_line(offset)
116
+ source_reader.source_line(line_no + offset)
117
+ end
118
+
66
119
  def file_name
67
120
  source_reader.file
68
121
  end
@@ -82,6 +135,8 @@ CqlRuby::SourceReader = Struct.new(:file) do
82
135
  end
83
136
 
84
137
  def source_line(n)
138
+ return nil unless lines.size >= n
139
+
85
140
  lines[n - 1].chop
86
141
  end
87
142
 
@@ -7,6 +7,7 @@ module CqlRuby
7
7
  [
8
8
  pass_type?(filter_reader, ancestors),
9
9
  pass_nesting?(filter_reader, ancestors),
10
+ pass_has?(filter_reader, node, ancestors),
10
11
  ].all?
11
12
  end
12
13
 
@@ -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]
@@ -49,6 +50,79 @@ module CqlRuby
49
50
  end
50
51
  end
51
52
  end
53
+
54
+ #
55
+ # @param [CqlRuby::FilterReader] filter_reader
56
+ # @param [Parser::AST::Node] node
57
+ # @param [Array<Parser::AST::Node>] ancestors
58
+ #
59
+ def pass_has?(filter_reader, node, ancestors)
60
+ return true unless filter_reader.restrict_children?
61
+
62
+ filter_reader.has_leaves.all? do |has_rule|
63
+ anchor_node = try_get_class(ancestors) || try_get_module(ancestors) || try_get_def(ancestors)
64
+ next false unless anchor_node
65
+
66
+ has_node_with_name?(anchor_node, has_rule)
67
+ end
68
+ end
69
+
70
+ #
71
+ # @param [Array<Parser::AST::Node>] ancestors
72
+ #
73
+ def try_get_class(ancestors)
74
+ return nil unless ancestors.size >= 2
75
+ return nil unless ancestors[-1].type == :const
76
+ return nil unless ancestors[-2].type == :class
77
+
78
+ ancestors[-2].children[2]
79
+ end
80
+
81
+ #
82
+ # @param [Array<Parser::AST::Node>] ancestors
83
+ #
84
+ def try_get_module(ancestors)
85
+ return nil unless ancestors.size >= 2
86
+ return nil unless ancestors[-1].type == :const
87
+ return nil unless ancestors[-2].type == :module
88
+
89
+ ancestors[-2].children[1]
90
+ end
91
+
92
+ #
93
+ # @param [Array<Parser::AST::Node>] ancestors
94
+ #
95
+ def try_get_def(ancestors)
96
+ return nil unless ancestors.size >= 1
97
+ return nil unless ancestors[-1].type == :def
98
+
99
+ ancestors[-1].children[2]
100
+ end
101
+
102
+ #
103
+ # @param [Parser::AST::Node] anchor_node
104
+ # @param [CqlRuby::NodeSpec]
105
+ #
106
+ def has_node_with_name?(anchor_node, has_rule)
107
+ return false unless anchor_node.is_a?(Parser::AST::Node)
108
+
109
+ fn_children_with_type = ->(node) { node.children.map { |child| [child, node.type] } }
110
+ to_visit = fn_children_with_type.call(anchor_node)
111
+
112
+ until to_visit.empty?
113
+ current_node, current_type = to_visit.shift
114
+
115
+ if current_node.is_a?(Parser::AST::Node)
116
+ to_visit += fn_children_with_type.call(current_node)
117
+ else
118
+ if current_type == has_rule.type.to_sym && CqlRuby::PatternMatcher.match?(has_rule.name, current_node)
119
+ return true
120
+ end
121
+ end
122
+ end
123
+
124
+ false
125
+ end
52
126
  end
53
127
  end
54
128
  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)
@@ -15,7 +19,7 @@ module CqlRuby
15
19
  end
16
20
 
17
21
  def self.regex?(pattern)
18
- pattern[0..1] == 'r:'
22
+ pattern[0..1] == 'r/'
19
23
  end
20
24
  private_class_method :regex?
21
25
 
@@ -26,7 +30,9 @@ module CqlRuby
26
30
 
27
31
  def self.regex_match?(pattern, subject)
28
32
  pattern = pattern[2..]
29
- pattern, *mods = pattern.split('+')
33
+ delim_idx = pattern.rindex('/')
34
+ mods = pattern[delim_idx + 1..].chars
35
+ pattern = pattern[0..delim_idx - 1]
30
36
 
31
37
  fops = 0
32
38
  fops |= Regexp::IGNORECASE if mods.include?('i')
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.3
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - itarato
@@ -9,7 +9,27 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
  date: 2020-07-05 00:00:00.000000000 Z
12
- dependencies: []
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: parser
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.7'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 2.7.1
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '2.7'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 2.7.1
13
33
  description:
14
34
  email: it.arato@gmail.com
15
35
  executables:
@@ -38,7 +58,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
38
58
  requirements:
39
59
  - - ">="
40
60
  - !ruby/object:Gem::Version
41
- version: '0'
61
+ version: 2.5.0
42
62
  required_rubygems_version: !ruby/object:Gem::Requirement
43
63
  requirements:
44
64
  - - ">="