| # 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 |