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