bin/proto2yml
#!/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)