protodec/src/protodec.cr
2019-10-26 11:16:05 -04:00

124 lines
3.6 KiB
Crystal

# protodec (which is a command-line decoder for arbitrary protobuf data)
# Copyright (C) 2019 Omar Roth
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
require "option_parser"
require "./protodec/utils"
CURRENT_BRANCH = {{ "#{`git branch | sed -n '/\* /s///p'`.strip}" }}
CURRENT_COMMIT = {{ "#{`git rev-list HEAD --max-count=1 --abbrev-commit`.strip}" }}
CURRENT_VERSION = {{ "#{`git describe --tags --abbrev=0`.strip}" }}
SOFTWARE = {
"name" => "protodec",
"version" => "#{CURRENT_VERSION}-#{CURRENT_COMMIT}",
"branch" => "#{CURRENT_BRANCH}",
}
enum IOType
Base64
Hex
Raw
Json
JsonPretty
end
input_type = nil
output_type = nil
flags = [] of String
OptionParser.parse do |parser|
parser.banner = <<-'END_USAGE'
Usage: protodec [arguments]
Command-line encoder and decoder for arbitrary protobuf data. Reads from standard input.
END_USAGE
parser.on("-e", "--encode", "Encode input") { flags << "e" }
parser.on("-d", "--decode", "Decode input (default)") { flags << "d" }
parser.on("-b", "--base64", "STDIN is Base64-encoded") { flags << "b" }
parser.on("-x", "--hex", "STDIN is space-separated hexstring") { flags << "x" }
parser.on("-r", "--raw", "STDIN is raw binary data (default)") { flags << "r" }
parser.on("-p", "--pretty", "Pretty print output") { flags << "p" }
parser.on("-h", "--help", "Show this help") { STDOUT.puts parser; exit(0) }
parser.invalid_option do |option|
flags += option.split("")[1..-1]
end
end
flags.each do |flag|
case flag
when "b"
input_type = IOType::Base64
when "x"
input_type = IOType::Hex
when "r"
input_type = IOType::Raw
when "p"
output_type = IOType::JsonPretty
when "e", "d"
when "v"
if flags.includes? "p"
STDOUT.puts SOFTWARE.to_pretty_json
else
STDOUT.puts SOFTWARE.to_json
end
exit(0)
else
STDERR.puts "ERROR: #{flag} is not a valid option."
exit(1)
end
end
if flags.includes? "e"
tmp = output_type
output_type = input_type
input_type = tmp
input_type ||= IOType::Json
output_type ||= IOType::Base64
else
input_type ||= IOType::Raw
output_type ||= IOType::Json
end
case input_type
when IOType::Base64
output = Protodec::Any.parse(IO::Memory.new(Base64.decode(URI.decode_www_form(URI.decode_www_form(STDIN.gets_to_end.strip)))))
when IOType::Hex
array = STDIN.gets_to_end.strip.split(/[- ,]+/).map &.to_i(16).to_u8
output = Protodec::Any.parse(IO::Memory.new(Slice.new(array.size) { |i| array[i] }))
when IOType::Raw
output = Protodec::Any.parse(IO::Memory.new(STDIN.gets_to_end))
when IOType::Json, IOType::JsonPretty
output = IO::Memory.new
Protodec::Any.from_json(JSON.parse(STDIN), output)
else
output = ""
end
case output_type
when IOType::Base64
STDOUT.puts Base64.urlsafe_encode(output.as(IO))
when IOType::Hex
STDOUT.puts (output.as(IO).to_slice.map &.to_s(16).rjust(2, '0').upcase).join("-")
when IOType::Raw
STDOUT.write output.as(IO).to_slice
when IOType::Json
STDOUT.puts output.as(Protodec::Any).to_json
when IOType::JsonPretty
STDOUT.puts output.as(Protodec::Any).to_pretty_json
end