|  | # This script parses the comments in the Ruby C extension files and translates them into Ruby | 
|  | # stubs. It looks for an instance of `ruby-doc:` and takes the following string as the object | 
|  | # under documentation (a class, instance method, or class method). | 
|  | # The intention of generating these files is for use by YARD to auto-generate documentation | 
|  | # here: https://www.rubydoc.info/gems/google-protobuf/ | 
|  | # | 
|  | # Because these comments are essentially "pretending" to be Ruby, we had to add one additional | 
|  | # YARD tag, `@paramdefault`, which is not a standard YARD tag. This tag is used to specify default | 
|  | # values for parameters. | 
|  |  | 
|  | require 'erb' | 
|  |  | 
|  | files = Dir.glob('ext/**/*.c') | 
|  | classes = {} | 
|  | MethodDefn = Struct.new(:name, :body, keyword_init: true) do | 
|  | def params | 
|  | params = body.scan(/@param (\S+) /).map { |p| p[0]} | 
|  | defaults = body.scan(/@paramdefault (\S+) (\S+)/).map { |p| [p[0], p[1]]}.to_h | 
|  | params = params.map { |p| p == 'kwargs' ? '**kwargs' : p } | 
|  | params = params.map { |p| defaults[p] ? "#{p}=#{defaults[p]}" : p } | 
|  | if body.include?('@yield') | 
|  | params.push('&block') | 
|  | end | 
|  | params | 
|  | end | 
|  | end | 
|  |  | 
|  | Defn = Struct.new(:name, :instance_meths, :class_meths, :body) do | 
|  | def initialize(*args, **kwargs) | 
|  | super | 
|  | self.instance_meths ||= [] | 
|  | self.class_meths ||= [] | 
|  | end | 
|  | end | 
|  |  | 
|  | files.each do |file| | 
|  | defs = File.read(file).scan(/ruby-doc:(.*?)\n(.*?)\*\//m) | 
|  | defs.each do |definition| | 
|  | name = definition[0].strip | 
|  | body = definition[1].strip. | 
|  | gsub(/^\s*\*/, ''). | 
|  | gsub(/\n/m, "\n  # ").strip | 
|  | if name.include?('.') | 
|  | klass, method_name = name.split('.') | 
|  | method = MethodDefn.new(name: method_name, body: body) | 
|  | classes[klass] ||= Defn.new(name: klass) | 
|  | classes[klass].class_meths.push(method) | 
|  | elsif name.include?('#') | 
|  | klass, method_name = name.split('#') | 
|  | method = MethodDefn.new(name: method_name, body: body) | 
|  | classes[klass] ||= Defn.new(name: klass) | 
|  | classes[klass].instance_meths.push(method) | 
|  | else | 
|  | classes[name] = Defn.new(name: name, body: body) | 
|  | end | 
|  | end | 
|  | end | 
|  |  | 
|  | # copied from ActiveSupport::Inflector | 
|  | def underscore(str) | 
|  | regex = /(?:(?<=([A-Za-z\d]))|\b)((?=a)b)(?=\b|[^a-z])/ | 
|  | str.gsub(regex) { "#{$1 && '_' }#{$2.downcase}" }. | 
|  | gsub(/([A-Z])(?=[A-Z][a-z])|([a-z\d])(?=[A-Z])/) { ($1 || $2) << "_" }. | 
|  | downcase | 
|  | end | 
|  |  | 
|  | class_erb = <<-ERB | 
|  | # This file was generated by generate_stubs.rb | 
|  | # Do not edit this file directly. | 
|  |  | 
|  | <%- if defn.body -%> | 
|  | <%= defn.body.gsub('  #', '#').gsub('#  ', '# ') %> | 
|  | <%- end -%> | 
|  | class Google::Protobuf::<%= name %> | 
|  | <%- defn.instance_meths.each do |meth| -%> | 
|  |  | 
|  | <%= meth.body.gsub('#  ', '# ') %> | 
|  | def <%= meth.name %><% if meth.params.any? %>(<%= meth.params.join(', ') %>)<% end %>; end | 
|  | <%- end -%> | 
|  | <%- defn.class_meths.each do |meth| -%> | 
|  |  | 
|  | <%= meth.body.gsub('#  ', '# ') %> | 
|  | def self.<%= meth.name %><% if meth.params.any? %>(<%= meth.params.join(', ') %>)<% end %>; end | 
|  | <%- end -%> | 
|  |  | 
|  | end # class Google::Protobuf::<%= name %> | 
|  | ERB | 
|  |  | 
|  | classes.each do |name, defn| | 
|  | file = File.join('lib/stubs', "#{underscore(name)}.rb") | 
|  | File.open(file, 'w') do |f| | 
|  | result = ERB.new(class_erb, trim_mode: '-').result(binding) | 
|  | f.puts result | 
|  | end | 
|  | end |