blob: b7d074c71f141febad3561d75a0051778f43099d [file] [log] [blame]
# 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