class Object

Constants

AUTHOR_EMAIL
AUTHOR_GITHUB
AUTHOR_INFO
AUTHOR_NAME
VERSION

Public Instance Methods

compile_proto(filename) click to toggle source
# File bin/proto-convert, line 43
def compile_proto(filename)
  puts "\n>> Compile .proto file" if $verbose

  file_path = File.expand_path(filename)
  file_dir = File.dirname(file_path)

  if $verbose
    protoc_version = `protoc --version`.chomp.split(' ')[1]
    puts "   protoc version  : #{protoc_version}"
    puts "   proto file path : #{file_path}"
  end

  protoc_cmd =
    "     protoc \\\n" \
    "       --ruby_out=#{file_dir} \\\n" \
    "       --proto_path=#{file_dir} \\\n" \
    "       #{file_path}"

  puts "\n#{protoc_cmd}" if $verbose

  `#{protoc_cmd}`
  abort "ERROR: Invalid schema! [#{filename}] Resolve error(s)." unless $CHILD_STATUS.success?

  compiled_proto = "#{file_dir}/#{File.basename(file_path, '.proto')}_pb.rb"
  abort "ERROR: Compiled schema not found! [#{compiled_proto}]" unless File.file?(compiled_proto)

  puts "\n   generated file  : #{compiled_proto}" if $verbose

  compiled_proto
end
convert(compiled_proto, args) click to toggle source
# File bin/proto-convert, line 104
def convert(compiled_proto, args)
  msg_type = args[:msgtype]
  conversion_mode = args[:mode]
  input_file = args[:input]
  output_file = args[:output]

  if $verbose
    puts "\n>> Convert"
    puts "   file : #{input_file}"
    puts "   mode : #{conversion_mode}"
  end

  pb_msg_class = msg_class(compiled_proto, msg_type)
  abort "ERROR: Message type ['#{msg_type}'] not registered!'" if pb_msg_class.nil?

  begin
    case conversion_mode
    when :binary2json
      input_msg = File.open(input_file, 'rb').read
      puts ">> [B] #{input_file} (#{input_msg.length} bytes)"

      decoded_msg = pb_msg_class.decode(input_msg)
      output_msg = pb_msg_class.encode_json(decoded_msg)

      File.open(output_file, 'w').write(output_msg)
      puts "<< [J] #{output_file} (#{output_msg.length} bytes)"
    when :json2binary
      input_msg = File.open(input_file, 'r').read
      puts ">> [J] #{input_file} (#{input_msg.length} bytes)"

      decoded_msg = pb_msg_class.decode_json(input_msg)
      output_msg = pb_msg_class.encode(decoded_msg)

      File.open(output_file, 'wb').write(output_msg)
      puts "<< [B] #{output_file} (#{output_msg.length} bytes)"
    end
  rescue Google::Protobuf::ParseError
    abort "ERROR: Incompatible input message! [msgtype: #{msg_type}] #{$ERROR_INFO}"
  rescue StandardError
    abort "ERROR: Conversion failed! #{$ERROR_INFO}"
  end

  puts ">> Converted! [#{input_file}] => [#{output_file}]" if $verbose
end
msg_class(compiled_proto, msg_type) click to toggle source
# File bin/proto-convert, line 98
def msg_class(compiled_proto, msg_type)
  require compiled_proto
  msg = Google::Protobuf::DescriptorPool.generated_pool.lookup(msg_type)
  msg.msgclass
end
start() click to toggle source
# File bin/proto-convert, line 149
def start
  mandatory_args = %i[mode proto msgtype input output].freeze

  args = {}
  mandatory_args.each { |arg| args[arg] = nil }

  parser = OptionParser.new do |opts|
    opts.banner = "Usage: #{$PROGRAM_NAME} -m [mode] -p [proto] -t [msgtype] -i [input] -o [output]"
    opts.separator "\nOPTIONS:\n\n"

    modes = %i[binary2json b2j json2binary j2b].freeze
    opts.on('-m', '--mode [MODE]', String, "conversion mode #{modes.map(&:to_s)}") do |mode|
      abort 'ERROR: Missing mode!' if mode.nil?

      mode = mode.to_sym
      abort "ERROR: Invalid mode! [#{mode}]" unless modes.include?(mode)

      case mode
      when :b2j
        mode = :binary2json
      when :j2b
        mode = :json2binary
      end

      args[:mode] = mode
    end

    opts.on('-p', '--proto [FILENAME]', String, 'protobuf schema (.proto)') do |filename|
      abort 'ERROR: Missing schema filename!' if filename.nil?
      abort "ERROR: Protobuf schema not found! [#{filename}]" unless File.file?(filename)

      args[:proto] = filename
    end

    opts.on('-t', '--msgtype [TYPE]', String, 'fully-qualified message type') do |msgtype|
      abort 'ERROR: Missing msgtype!' if msgtype.nil?

      args[:msgtype] = msgtype
    end

    opts.on('-i', '--input [FILENAME]', String, 'source file (JSON/binary)') do |filename|
      abort 'ERROR: Missing input filename!' if filename.nil?
      abort "ERROR: Input file not found! [#{filename}]" unless File.file?(filename)

      args[:input] = filename
    end

    opts.on('-o', '--output [FILENAME]', String, 'destination file (binary/JSON)') do |filename|
      abort 'ERROR: Missing output filename!' if filename.nil?

      args[:output] = filename
    end

    opts.on('-v', '--verbose', 'print verbose information') do
      $verbose = true
    end

    opts.on('-h', '--help', 'print help') do
      puts "#{$PROGRAM_NAME} #{VERSION}\n\n#{opts}\n#{AUTHOR_INFO}\n#{AUTHOR_GITHUB}"
      exit
    end
  end

  begin
    parser.parse!

    puts ">> #{$PROGRAM_NAME} #{VERSION} [verbose mode]" if $verbose

    # Validate missing mandatory arguments
    missing_args = mandatory_args.select { |arg| args[arg].nil? }
    abort "ERROR: Missing required arguments! --#{missing_args.join(', --')}" unless missing_args.empty?

    if $verbose
      puts "\n>> Arguments"
      args.each do |arg, val|
        puts format('   %<arg>8s : %<val>s', arg: arg, val: val)
      end
    end

    # Compile and validate proto and msgtype
    compiled_proto = compile_proto(args[:proto])
    abort unless valid_msgtype?(compiled_proto, args[:msgtype])

    convert(compiled_proto, args)
  rescue OptionParser::InvalidOption, OptionParser::InvalidArgument, OptionParser::MissingArgument
    puts $ERROR_INFO
    abort "\n#{$PROGRAM_NAME} #{VERSION}\n\n#{parser}\n#{AUTHOR_INFO}\n#{AUTHOR_GITHUB}"
  rescue LoadError
    abort "ERROR: Possible 'import' issue! #{$ERROR_INFO}"
  rescue StandardError
    abort "ERROR: #{$ERROR_INFO}"
  ensure
    File.delete(compiled_proto) if !compiled_proto.nil? && File.file?(compiled_proto)
  end
end
valid_msgtype?(compiled_proto, msg_type) click to toggle source
# File bin/proto-convert, line 74
def valid_msgtype?(compiled_proto, msg_type)
  if $verbose
    puts "\n>> Validate msgtype"
    puts "   msgtype : #{msg_type}"
    puts "   pb file : #{compiled_proto}"
  end

  msg_types = []
  File.foreach(compiled_proto) do |line|
    if line.lstrip.start_with?('add_message')
      extracted_msg_type = line[/"([^"]*)"/, 1].freeze # regex: <add_message> 'msg_type' <do>
      msg_types.push(extracted_msg_type) unless extracted_msg_type.nil?
    end
  end

  is_valid = msg_types.include?(msg_type)
  unless is_valid
    puts "ERROR: Invalid msgtype! [#{msg_type}]"
    puts "       Available types: #{msg_types}"
  end

  is_valid
end