# 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