pogodevorg/docs.pogodev.org

View on GitHub
bin/proto2yml

Summary

Maintainability
Test Coverage
#!/usr/bin/env ruby
# Simple quick and dirty regexp parsing of proto files.
# Output a YAML data file

require 'optparse'
require 'ostruct'
require 'yaml'
require 'active_support/all'

# Nested to_h for OpenStruct
class OpenStruct
  def to_hash(hash = {})
    self.each_pair do |key, value|
      key = key.to_s
      if value.is_a?(OpenStruct)
        hash[key] = value.to_hash
      elsif value.is_a?(Array)
        hash[key] = value.map(&:to_hash)
      else
        hash[key] = value
      end
    end
    hash
  end
end

USAGE = "Usage: #{__FILE__} file.proto"
options = {}
OptionParser.new do |opts|
  opts.banner = USAGE

  opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
    options[:verbose] = v
  end
end.parse!

file_arg = ARGV[0]
puts USAGE unless file_arg

if !File.exists?(file_arg)
  puts "#{__FILE__}: #{file_arg}: No such file"
  exit 1
end

proto = IO.readlines(file_arg)

global = OpenStruct.new(enums: [], messages: [])

context = global
stack = [global]

proto.each_with_index do |line, i|
  # ignore
  next if line =~ /^syntax/
  next if line =~ /^package/
  next if line =~ /^\s*$/
  next if line =~ /^\s*{\s*$/

  # Parse enums
  if m = line.match(/^\s*enum\s*(?<name>[[:graph:]]+)\s*$/)
    if stack.last == global
      puts "Enum: #{m['name']}" if options[:verbose]
    else
      puts "Enum: #{m['name']} (in #{stack.last.name})" if options[:verbose]
    end
    enum = OpenStruct.new(name: m['name'], sort_title: m['name'].underscore, values: [])
    stack.last.enums.push enum
    stack.push enum
    next
  end

  # Parse messages
  if m = line.match(/^message\s*(?<name>[[:graph:]]+)\s*$/)
    puts "Message: #{m['name']}" if options[:verbose]
    message = OpenStruct.new(name: m['name'], sort_title: m['name'].underscore, attributes: [], enums: [])
    stack.last.messages.push message
    stack.push message
    next
  end

  # Parse message attributes
  if m = line.match(/^\s*((?<modifier>[[:graph:]]+)\s+){0,1}(?<type>[[:graph:]]+)\s+(?<name>[[:graph:]]+)\s+=\s+(?<tag>[[:graph:]]+);\s*$/)
    puts "  Attribute: #{m['name']} (#{m['type']}) #{m['tag']}" if options[:verbose]
    attribute = OpenStruct.new(type: m['type'], name: m['name'], tag: m['tag'].to_i, modifier: m['modifier'])
    stack.last.attributes.push attribute
    next
  end

  # Parse enum values
  if m = line.match(/^\s*(?<name>[[:graph:]]+)\s+=\s+(?<tag>[[:graph:]]+);\s*$/)
    puts "  Enum value: #{m['name']} (#{m['tag']})" if options[:verbose]
    value = OpenStruct.new(name: m['name'], tag: m['tag'].to_i)
    stack.last.values.push value
    next
  end

  if m = line.match(/^\s*}\s*$/)
    stack.pop
    next
  end

  STDERR.puts "Warn: unmatched line #{i}: #{line}"
end

# Sort stuff a bit
global.enums.sort_by!(&:name)
global.enums.each{|e| e.values.sort_by!(&:tag)}
global.messages.sort_by!(&:name)
global.messages.each{|m| m.attributes.sort_by!(&:tag)}
global.messages.each{|m| m.enums.sort_by!(&:name)}
global.messages.each{|m| m.enums.each{|e| e.values.sort_by!(&:tag)}}

puts YAML.dump(global.to_hash)