740 lines
34 KiB
Python
Executable File
740 lines
34 KiB
Python
Executable File
#!/usr/bin/python
|
|
################################################################################
|
|
# ____ _ _ #
|
|
# | _ \ _ __ ___ | |_ ___ ___ ___ | | #
|
|
# | |_) | '__/ _ \| __/ _ \ / __/ _ \| | #
|
|
# | __/| | | (_) | || (_) | (_| (_) | | #
|
|
# |_| |_| \___/ \__\___/ \___\___/|_| #
|
|
# #
|
|
# == A Simple ASCII Header Generator for Network Protocols == #
|
|
# #
|
|
################################################################################
|
|
# #
|
|
# Written by: #
|
|
# #
|
|
# Luis MartinGarcia. #
|
|
# -> E-Mail: luis.mgarc@gmail.com #
|
|
# -> WWWW: http://www.luismg.com #
|
|
# -> GitHub: https://github.com/luismartingarcia #
|
|
# #
|
|
################################################################################
|
|
# #
|
|
# This file is part of Protocol. #
|
|
# #
|
|
# Copyright (C) 2014 Luis MartinGarcia (luis.mgarc@gmail.com) #
|
|
# #
|
|
# 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 <http://www.gnu.org/licenses/>. #
|
|
# #
|
|
# Please check file LICENSE.txt for the complete version of the license, #
|
|
# as this disclaimer does not contain the full information. Also, note #
|
|
# that although Protocol is licensed under the GNU GPL v3 license, it may #
|
|
# be possible to obtain copies of it under different, less restrictive, #
|
|
# alternative licenses. Requests will be studied on a case by case basis. #
|
|
# If you wish to obtain Protocol under a different license, please contact #
|
|
# the email address mentioned above. #
|
|
# #
|
|
################################################################################
|
|
# #
|
|
# Description: #
|
|
# #
|
|
# Protocol is a command-line tool that provides quick access to the most #
|
|
# common network protocol headers in ASCII (RFC-like) format. It also has the #
|
|
# ability to create ASCII headers for custom protocols defined by the user #
|
|
# through a very simple syntax. #
|
|
# #
|
|
################################################################################
|
|
|
|
# STANDARD LIBRARY IMPORTS
|
|
import sys
|
|
from datetime import date
|
|
|
|
# INTERNAL IMPORTS
|
|
from constants import *
|
|
import specs
|
|
|
|
|
|
# CLASS DEFINITIONS
|
|
class ProtocolException(Exception):
|
|
"""
|
|
This class represents exceptions raised by the Protocol class
|
|
"""
|
|
def __init__(self, errmsg):
|
|
self.errmsg=errmsg
|
|
|
|
def __str__(self):
|
|
return str(self.errmsg)
|
|
|
|
|
|
class Protocol():
|
|
"""
|
|
This class represents a network protocol header. Objects are constructed by
|
|
passing a textual protocol specification. Once that is done, instances
|
|
can be printed by converting them to a str type.
|
|
"""
|
|
|
|
def __init__(self, spec):
|
|
"""
|
|
Class constructor.
|
|
@param spec is the textual specification that describes the protocol.
|
|
"""
|
|
self.hdr_char_start="+" # Character for start of the border line
|
|
self.hdr_char_end="+" # Character for end of the border line
|
|
self.hdr_char_fill_odd="+" # Fill character for border odd positions
|
|
self.hdr_char_fill_even="-" # Fill character for border even positions
|
|
self.hdr_char_sep="|" # Field separator character
|
|
self.bits_per_line=32 # Number of bits per line
|
|
self.do_print_top_tens=True # True: print top numbers for bit tens
|
|
self.do_print_top_units=True # True: print top numbers for bit units
|
|
self.field_list=[] # Header fields to be printed out
|
|
self.parse_spec(spec) # Parse the received spec and populate self.field_list
|
|
|
|
|
|
def parse_spec(self, spec):
|
|
"""
|
|
Parses a textual protocol spec and stores the relevant internal state
|
|
so such spec can be later converted to a nice ASCII diagram.
|
|
@return the list of protocol fields, as a dictionary containing
|
|
keys 'len' and 'text'. The list is returned for completeness but no
|
|
caller is expected to store or use such list.
|
|
@raise ProtocolException in case the supplied spec is not valid
|
|
"""
|
|
if "?" in spec:
|
|
parts=spec.split("?")
|
|
fields=parts[0]
|
|
opts=parts[1]
|
|
if spec.count("?")>1:
|
|
raise ProtocolException("FATAL: Character '?' may only be used as an option separator.")
|
|
else:
|
|
fields=spec
|
|
opts=None
|
|
|
|
# Parse field spec
|
|
items=fields.split(",")
|
|
for item in items:
|
|
try:
|
|
text, bits = item.split(":")
|
|
bits=int(bits)
|
|
if bits<=0:
|
|
raise ProtocolException("FATAL: Fields must be at least one bit long (%s)" %spec)
|
|
except ProtocolException:
|
|
raise
|
|
except:
|
|
raise ProtocolException("FATAL: Invalid field_list specification (%s)" %spec)
|
|
self.field_list.append({"text":text, "len":bits})
|
|
|
|
# Parse options
|
|
if opts is not None:
|
|
opts=opts.split(",")
|
|
for opt in opts:
|
|
try:
|
|
var, value = opt.split("=")
|
|
if var.lower()=="bits":
|
|
self.bits_per_line=int(value)
|
|
if self.bits_per_line<=0:
|
|
raise ProtocolException("FATAL: Invalid value for 'bits' option (%s)" % value)
|
|
elif var.lower()=="numbers":
|
|
if value.lower() in ["0", "n", "no", "none", "false"]:
|
|
self.do_print_top_tens=False
|
|
self.do_print_top_units=False
|
|
elif value.lower() in ["1", "y", "yes", "none", "true"]:
|
|
self.do_print_top_tens=True
|
|
self.do_print_top_units=True
|
|
else:
|
|
raise ProtocolException("FATAL: Invalid value for 'numbers' option (%s)" % value)
|
|
elif var.lower() in ["oddchar", "evenchar", "startchar", "endchar", "sepchar"]:
|
|
if len(value)>1 or len(value)<=0:
|
|
raise ProtocolException("FATAL: Invalid value for '%s' option (%s)" % (var, value))
|
|
else:
|
|
if var.lower()=="oddchar":
|
|
self.hdr_char_fill_odd=value
|
|
elif var.lower()=="evenchar":
|
|
self.hdr_char_fill_even=value
|
|
elif var.lower()=="startchar":
|
|
self.hdr_char_start=value
|
|
elif var.lower()=="endchar":
|
|
self.hdr_char_end=value
|
|
elif var.lower()=="sepchar":
|
|
self.hdr_char_sep=value
|
|
except ProtocolException:
|
|
raise
|
|
except:
|
|
raise ProtocolException("FATAL: Invalid options specification (%s)" % opt)
|
|
|
|
return self.field_list
|
|
|
|
|
|
def _get_top_numbers(self):
|
|
"""
|
|
@return a string representing the bit units and bit tens on top of the
|
|
protocol header. Note that a proper string is only returned if one or
|
|
both self.do_print_top_tens and self.do_print_top_units is True.
|
|
The returned string is not \n terminated, but it may contain a newline
|
|
character in the middle.
|
|
"""
|
|
lines=["", ""]
|
|
if self.do_print_top_tens is True:
|
|
for i in range(0, self.bits_per_line):
|
|
if str(i)[-1:]=="0":
|
|
lines[0]+=" %s" % str(i)[0]
|
|
else:
|
|
lines[0]+=" "
|
|
lines[0]+="\n"
|
|
if self.do_print_top_units is True:
|
|
for i in range(0, self.bits_per_line):
|
|
lines[1]+=" %s" % str(i)[-1:]
|
|
#lines[1]+="\n"
|
|
result = "".join(lines)
|
|
return result if len(result)>0 else None
|
|
|
|
|
|
def _get_horizontal(self, width=None):
|
|
"""
|
|
@return the horizontal border line that separates field rows.
|
|
@param width controls how many field bits the line should cover. By
|
|
default, if no width is supplied, the line covers the hole length of
|
|
the header.
|
|
"""
|
|
if width is None:
|
|
width=self.bits_per_line
|
|
if width<=0:
|
|
return ""
|
|
else:
|
|
a="%s" % self.hdr_char_start
|
|
b=(self.hdr_char_fill_even+self.hdr_char_fill_odd)*(width-1)
|
|
c="%s%s" % (self.hdr_char_fill_even, self.hdr_char_end)
|
|
return a+b+c
|
|
|
|
|
|
def _get_separator(self, line_end=""):
|
|
"""
|
|
@return a string containing a protocol field separator. Returned string
|
|
is a single character and matches whatever is stored in self.hdr_char_sep
|
|
"""
|
|
return self.hdr_char_sep
|
|
|
|
|
|
def _process_field_list(self):
|
|
"""
|
|
Processes the list of protocol fields that we got from the spec and turns
|
|
it into something that we can print easily (useful for cases when we have
|
|
protocol fields that span more than one line). This is just a helper
|
|
function to make __str__()'s life easier.
|
|
"""
|
|
new_fields=[]
|
|
bits_in_line=0
|
|
i=0
|
|
while i < len(self.field_list):
|
|
# Extract all the info we need about the field
|
|
field=self.field_list[i]
|
|
field_text= field['text']
|
|
field_len= field['len']
|
|
field['MF']=False
|
|
|
|
available_in_line = self.bits_per_line - bits_in_line
|
|
|
|
# If we have enough space on this line to include the current field
|
|
# then just keep it as it is.
|
|
if available_in_line >= field_len:
|
|
new_fields.append(field)
|
|
bits_in_line+=field_len
|
|
i+=1
|
|
if bits_in_line==self.bits_per_line:
|
|
bits_in_line=0
|
|
# Otherwise, split the field into two parts, one blank and one with
|
|
# the actual field text
|
|
else:
|
|
|
|
# Case 1: We have a field that is perfectly aligned and it
|
|
# has a length that is multiple of our line length
|
|
if bits_in_line==0 and field_len%self.bits_per_line==0:
|
|
new_fields.append(field)
|
|
i+=1
|
|
bits_in_line=0
|
|
|
|
# Case 2: We weren't that lucky and the field is either not
|
|
# aligned or we can't print it using an exact number of full
|
|
# lines
|
|
else:
|
|
|
|
# If we have more space in the current line than in the next,
|
|
# then put the field text in this one
|
|
if available_in_line >= field_len-available_in_line:
|
|
new_field = {'text':field_text, 'len':available_in_line, "MF":True}
|
|
new_fields.append(new_field)
|
|
field['text']=""
|
|
field['len']=field_len-available_in_line
|
|
field['MF']=False
|
|
else:
|
|
new_field = {'text':"", 'len':available_in_line, "MF":True}
|
|
new_fields.append(new_field)
|
|
field['text']=field_text
|
|
field['len']=field_len-available_in_line
|
|
field['MF']=False
|
|
bits_in_line=0
|
|
continue
|
|
return new_fields
|
|
|
|
|
|
# Convert to string
|
|
def __str__(self):
|
|
"""
|
|
Converts the protocol specification stored in the object to a nice
|
|
ASCII diagram like the ones that appear in RFCs. Conversion supports
|
|
fields of any length, and supports field that span more than one
|
|
line in the diagram.
|
|
@return a string containing the ASCII representation of the protocol
|
|
header.
|
|
"""
|
|
|
|
# First of all, process our field list. This does some magic to make
|
|
# the algorithm work for fields that span more than one line
|
|
proto_fields = self._process_field_list()
|
|
lines=[]
|
|
numbers=self._get_top_numbers()
|
|
if numbers is not None:
|
|
lines.append(numbers)
|
|
lines.append(self._get_horizontal())
|
|
|
|
# Print all protocol fields
|
|
bits_in_line=0
|
|
current_line=""
|
|
fields_done=0
|
|
p=-1
|
|
while p < len(proto_fields)-1:
|
|
p+=1
|
|
|
|
# Extract all the info we need about the field
|
|
field = proto_fields[p]
|
|
field_text= field['text']
|
|
field_len= field['len']
|
|
field_mf = field['MF'] is True # Field has more fragments
|
|
|
|
# If the field text is too long, we truncate it, and add a dot
|
|
# at the end.
|
|
if len(field_text) > (field_len*2)-1:
|
|
field_text=field_text[0:(field_len*2)-1]
|
|
if len(field_text)>1:
|
|
field_text=field_text[0:-1]+"."
|
|
|
|
# If we have space for the whole field in the current line, go
|
|
# ahead and add it
|
|
if self.bits_per_line-bits_in_line >= field_len:
|
|
# If this is the first thing we print on a line, add the
|
|
# starting character
|
|
if bits_in_line==0:
|
|
current_line+=self._get_separator()
|
|
|
|
# Add the whole field
|
|
current_line+=str.center(field_text, (field_len*2)-1)
|
|
|
|
# Update counters
|
|
bits_in_line+=field_len
|
|
fields_done+=1
|
|
|
|
# If this is the last character in the line, store the line
|
|
if bits_in_line==self.bits_per_line:
|
|
current_line+=self._get_separator()
|
|
lines.append(current_line)
|
|
current_line=""
|
|
bits_in_line=0
|
|
# When we have a fragmented field, we may need to suppress
|
|
# the floor of the field, so the current line connects
|
|
# with the one that follows. E.g:
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
# | field16 | |
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +
|
|
# | field |
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
if field_mf is True:
|
|
if proto_fields[p+1]['len'] > self.bits_per_line - field_len:
|
|
|
|
# Print some +-+-+ to cover the previous field
|
|
line_left=self._get_horizontal(self.bits_per_line - field_len)
|
|
if len(line_left)==0:
|
|
line_left=self.hdr_char_start
|
|
|
|
# Now print some empty space to cover the part that
|
|
# we can join with the field below.
|
|
# Case 1: If the next field reaches the end of its
|
|
# line, then we need to print whitespace until the
|
|
# end our line
|
|
if proto_fields[p+1]['len'] >= self.bits_per_line:
|
|
line_center=" "* ((2*(field_len)-1))
|
|
line_right=self.hdr_char_end
|
|
# Case 2: the field in the next row is not big enough
|
|
# to cover all the space we'd like to join, so we
|
|
# just print whitespace to cover as much as we can
|
|
else:
|
|
line_center=" "* ((2*((proto_fields[p+1]['len']-(self.bits_per_line-field_len))))-1)
|
|
line_right=self._get_horizontal(self.bits_per_line-proto_fields[p+1]['len'])
|
|
|
|
lines.append(line_left+line_center+line_right)
|
|
else:
|
|
lines.append(self._get_horizontal())
|
|
else:
|
|
lines.append(self._get_horizontal())
|
|
|
|
|
|
# If this is not the last character of the line but we have no
|
|
# more fields to print, wrap up
|
|
elif fields_done==len(proto_fields):
|
|
current_line+=self._get_separator()
|
|
lines.append(current_line)
|
|
lines.append(self._get_horizontal(bits_in_line))
|
|
else:
|
|
# Add the separator character
|
|
current_line+=self.hdr_char_sep
|
|
|
|
# We don't have enough space for the field on this line.
|
|
else:
|
|
# Case 1: We are at the beginning of a new line and we need to
|
|
# span more than one line
|
|
if bits_in_line==0:
|
|
# Case 1a: We have a multiple of the number of bits per line
|
|
if field_len%self.bits_per_line==0:
|
|
# Compute how many lines in total we need to print for this
|
|
# big field.
|
|
lines_to_print = int(((field_len/self.bits_per_line)*2)-1)
|
|
# We print the field text in the central line
|
|
central_line=int(lines_to_print/2)
|
|
# Print all those lines
|
|
for i in range(0, lines_to_print):
|
|
|
|
# Let's figure out which character we need to use
|
|
# to start and end the current line
|
|
if i%2==1:
|
|
start_line=self.hdr_char_start
|
|
end_line=self.hdr_char_end
|
|
else:
|
|
start_line=self.hdr_char_sep
|
|
end_line=self.hdr_char_sep
|
|
|
|
# This is the line where we need to print the field
|
|
# text.
|
|
if i == central_line:
|
|
lines.append(start_line + str.center(field_text, (self.bits_per_line*2)-1) + end_line)
|
|
# This is a line we need to leave blank
|
|
else:
|
|
lines.append(start_line + (" " * ((self.bits_per_line*2)-1)) + end_line)
|
|
# If we just added the last line, add a horizontal separator
|
|
if i==lines_to_print-1:
|
|
lines.append(self._get_horizontal())
|
|
|
|
# Case 2: We are not at the beginning of the line and we need
|
|
# to print something that does not fit in the current line
|
|
else:
|
|
# This should never happen, since our _process_field_list()
|
|
# divides fields in chunks so we never have the case of
|
|
# something spanning lines in a weird manner
|
|
assert(False)
|
|
|
|
result= "\n".join(lines)
|
|
return result
|
|
|
|
|
|
class Main():
|
|
"""
|
|
This class does all the boring task of a command-line application. It parses
|
|
user input, displays usage, parses input files, etc.
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""
|
|
Class constructor. Nothing fancy.
|
|
"""
|
|
self.cmd_line_args=None # Copy of the user argv
|
|
self.protocols=[] # List of protocols to print out
|
|
self.bits_per_line=None # Number of bits per line to print
|
|
self.skip_numbers=None # True to avoid printing bit units and tens
|
|
self.hdr_char_start=None # Character for start of the border line
|
|
self.hdr_char_end=None # Character for end of the border line
|
|
self.hdr_char_fill_odd=None # Fill character for border odd positions
|
|
self.hdr_char_fill_even=None # Fill character for border even positions
|
|
self.hdr_char_sep=None # Field separator character
|
|
|
|
|
|
def display_help(self):
|
|
"""
|
|
Displays command-line usage help to standard output.
|
|
"""
|
|
print("")
|
|
print("%s v%s" % (APPLICATION_NAME, APPLICATION_VERSION))
|
|
print("Copyright (C) %i, %s (%s)." % (max(2014, date.today().year), APPLICATION_AUTHOR, APPLICATION_AUTHOR_EMAIL))
|
|
print("This software comes with ABSOLUTELY NO WARRANTY.")
|
|
print("")
|
|
self.display_usage()
|
|
print("PARAMETERS:")
|
|
print(" <protocol> : Name of an existing protocol")
|
|
print(" <spec> : Field by field specification of non-existing protocol")
|
|
print("OPTIONS:")
|
|
print(" -b, --bits <n> : Number of bits per line")
|
|
print(" -f, --file : Read specs from a text file")
|
|
print(" -h, --help : Displays this help information")
|
|
print(" -n, --no-numbers : Do not print bit numbers on top of the header")
|
|
print(" -V, --version : Displays current version")
|
|
print(" --evenchar <char> : Character for the even positions of horizontal table borders")
|
|
print(" --oddchar <char> : Character for the odd positions of horizontal table borders")
|
|
print(" --startchar <char> : Character that starts horizontal table borders")
|
|
print(" --endchar <char> : Character that ends horizontal table borders")
|
|
print(" --sepchar <char> : Character that separates protocol fields")
|
|
|
|
|
|
def get_usage(self):
|
|
"""
|
|
@return a string containing application usage information
|
|
"""
|
|
return "Usage: %s {<protocol> or <spec>} [OPTIONS]" % self.cmd_line_args[0]
|
|
|
|
|
|
def display_usage(self):
|
|
"""
|
|
Prints usage information to standard output
|
|
"""
|
|
print(self.get_usage())
|
|
|
|
|
|
def parse_config_file(self, filename):
|
|
"""
|
|
This method parses the supplied configuration file and adds any protocols to our
|
|
list of protocols to print.
|
|
@return The number of protocols parsed
|
|
"""
|
|
|
|
i=0
|
|
# Read the contents of the whole file
|
|
try:
|
|
f = open(filename)
|
|
lines = f.readlines()
|
|
f.close()
|
|
except:
|
|
print("Error while reading file %s. Please make sure it exists and it's readable." % filename)
|
|
sys.exit(1)
|
|
|
|
# Parse protocol specs, line by line
|
|
for line in lines:
|
|
# Sanitize the line
|
|
line=line.strip()
|
|
|
|
# If it starts with #, or is an empty line ignore it
|
|
if line.startswith("#") or len(line)==0:
|
|
continue
|
|
|
|
# If we have something else, treat it as a protocol spec
|
|
proto=Protocol(line)
|
|
self.protocols.append(proto)
|
|
i+=1
|
|
|
|
return i
|
|
|
|
|
|
def parse_cmd_line_args(self, argv, is_config_file=False):
|
|
"""
|
|
Parses command-line arguments and stores any relevant information
|
|
internally
|
|
"""
|
|
|
|
# Store a reference to command line args for later use.
|
|
if is_config_file==False:
|
|
self.cmd_line_args = argv
|
|
|
|
# Check we have received enough command-line parameters
|
|
if len(argv) == 1 and is_config_file is False:
|
|
print(self.get_usage())
|
|
sys.exit(1)
|
|
else:
|
|
skip_arg=False
|
|
for i in range(1, len(argv)):
|
|
|
|
# Useful for args like -c <filename>. This avoids parsing the
|
|
# filename as it if was a command-line flag.
|
|
if skip_arg==True:
|
|
skip_arg=False
|
|
continue
|
|
|
|
# Spec file
|
|
if argv[i]=="-f" or argv[i]=="--file":
|
|
# Make sure we have an actual parameter after the flag
|
|
if (i+1)>=len(argv):
|
|
return OP_FAILURE, "Expected parameter after %s\n%s" % (argv[i], self.get_usage())
|
|
skip_arg=True
|
|
# Parse the config file
|
|
protos = self.parse_config_file(argv[i+1])
|
|
if protos <= 0:
|
|
return OP_FAILURE, "No protocol specifications found in the supplied file (%s)" % argv[i+1]
|
|
|
|
# Bits per line
|
|
elif argv[i]=="-b" or argv[i]=="--bits":
|
|
# Make sure we have an actual parameter after the flag
|
|
if (i+1)>=len(argv):
|
|
return OP_FAILURE, "Expected parameter after %s\n%s" % (argv[i], self.get_usage())
|
|
skip_arg=True
|
|
# Parse the config file
|
|
try:
|
|
self.bits_per_line=int(argv[i+1])
|
|
if self.bits_per_line<=0:
|
|
return OP_FAILURE, "Invalid number of bits per line supplied (%s)" % argv[i+1]
|
|
except:
|
|
return OP_FAILURE, "Invalid number of bits per line supplied (%s)" % argv[i+1]
|
|
|
|
# Avoid displaying numbers on top of the header
|
|
elif argv[i]=="-n" or argv[i]=="--no-numbers":
|
|
self.skip_numbers=True
|
|
|
|
# Character variations
|
|
elif argv[i] in ["--oddchar", "--evenchar", "--startchar", "--endchar", "--sepchar"]:
|
|
# Make sure we have an actual parameter after the flag
|
|
if (i+1)>=len(argv):
|
|
return OP_FAILURE, "Expected parameter after %s\n%s" % (argv[i], self.get_usage())
|
|
skip_arg=True
|
|
|
|
# Make sure we got a single character, not more
|
|
if len(argv[i+1])!=1:
|
|
return OP_FAILURE, "A single character is expected after %s\n%s" % (argv[i], self.get_usage())
|
|
|
|
# Now let's store whatever character spec we got
|
|
if argv[i]=="--oddchar":
|
|
self.hdr_char_fill_odd=argv[i+1]
|
|
elif argv[i]=="--evenchar":
|
|
self.hdr_char_fill_even=argv[i+1]
|
|
elif argv[i]=="--startchar":
|
|
self.hdr_char_start=argv[i+1]
|
|
elif argv[i]=="--endchar":
|
|
self.hdr_char_end=argv[i+1]
|
|
elif argv[i]=="--sepchar":
|
|
self.hdr_char_sep=argv[i+1]
|
|
|
|
# Display help
|
|
elif argv[i]=="-h" or argv[i]=="--help":
|
|
self.display_help()
|
|
sys.exit(0)
|
|
|
|
# Display version
|
|
elif argv[i]=="-V" or argv[i]=="--version":
|
|
print("%s v%s" % (APPLICATION_NAME, APPLICATION_VERSION))
|
|
sys.exit(0)
|
|
|
|
# Incorrect option supplied
|
|
elif argv[i].startswith("-"):
|
|
print("ERROR: Invalid option supplied (%s)" % argv[i])
|
|
sys.exit(1)
|
|
|
|
# Protocol name or protocol spec
|
|
else:
|
|
# If it contains ":" characters, we have a protocol spec
|
|
if argv[i].count(":")>0:
|
|
spec = argv[i]
|
|
# Otherwise, the user meant to display an existing protocol
|
|
else:
|
|
# If we got an exact match, end of story
|
|
if argv[i] in specs.protocols:
|
|
spec = specs.protocols[argv[i]]
|
|
# Otherwise, we may have received a partial match so
|
|
# we need to figure out which protocol the user meant.
|
|
# If the specification is ambiguous, we will error
|
|
else:
|
|
start_with_the_same=[]
|
|
for spec in specs.protocols:
|
|
if spec.startswith(argv[i]):
|
|
start_with_the_same.append(spec)
|
|
# If we only have one entry, it means we got some
|
|
# shortened version of the protocol name but no
|
|
# ambiguity. In that case, we will use the match.
|
|
if len(start_with_the_same)==1:
|
|
spec=specs.protocols[start_with_the_same[0]]
|
|
elif len(start_with_the_same)==0:
|
|
print("ERROR: supplied protocol '%s' does not exist." % argv[i]);
|
|
sys.exit(1)
|
|
else:
|
|
print("Ambiguous protocol specifier '%s'. Did you mean any of these?" % argv[i])
|
|
for spec in start_with_the_same:
|
|
print(" %s" % spec)
|
|
sys.exit(1)
|
|
# Finally, based on the spec, instance an actual protocol.
|
|
# Note that if the spec is incorect, the Protocol() consutrctor
|
|
# will call sys.exit() itself, so there is no need to do
|
|
# error checking here.
|
|
try:
|
|
proto = Protocol(spec)
|
|
self.protocols.append(proto)
|
|
except ProtocolException as e:
|
|
print("ERROR: %s" % str(e))
|
|
sys.exit(1)
|
|
|
|
if len(self.protocols)==0:
|
|
print("ERROR: Missing protocol")
|
|
sys.exit(1)
|
|
|
|
return OP_SUCCESS, None
|
|
|
|
|
|
|
|
def run(self):
|
|
"""
|
|
This is Protocol's 'core' method: parses command line argument and prints
|
|
any necessary protocol to standard output
|
|
"""
|
|
|
|
# Parse command-line arguments
|
|
code, err = self.parse_cmd_line_args(sys.argv)
|
|
if code!=OP_SUCCESS:
|
|
print("ERROR: %s" % err)
|
|
sys.exit(1)
|
|
|
|
# Print the appropriate protocol headers
|
|
for i in range(0, len(self.protocols)):
|
|
|
|
# Modify the properties of the object if the user passed any
|
|
# options that require it
|
|
if self.bits_per_line is not None:
|
|
self.protocols[i].bits_per_line = self.bits_per_line
|
|
if self.skip_numbers is not None:
|
|
if self.skip_numbers is True:
|
|
self.protocols[i].do_print_top_tens=False
|
|
self.protocols[i].do_print_top_units=False
|
|
else:
|
|
self.protocols[i].do_print_top_tens=True
|
|
self.protocols[i].do_print_top_units=True
|
|
if self.hdr_char_end is not None:
|
|
self.protocols[i].hdr_char_end=self.hdr_char_end
|
|
if self.hdr_char_start is not None:
|
|
self.protocols[i].hdr_char_start=self.hdr_char_start
|
|
if self.hdr_char_fill_even is not None:
|
|
self.protocols[i].hdr_char_fill_even=self.hdr_char_fill_even
|
|
if self.hdr_char_fill_odd is not None:
|
|
self.protocols[i].hdr_char_fill_odd=self.hdr_char_fill_odd
|
|
if self.hdr_char_sep is not None:
|
|
self.protocols[i].hdr_char_sep=self.hdr_char_sep
|
|
|
|
print(self.protocols[i])
|
|
if len(self.protocols)>1 and i!=len(self.protocols)-1:
|
|
print("")
|
|
|
|
|
|
# Main function
|
|
def main():
|
|
"""
|
|
Main function. Runs the Protocol program.
|
|
"""
|
|
|
|
# Instance our core class
|
|
program = Main()
|
|
|
|
# Do our magic
|
|
program.run()
|
|
|
|
|
|
# THIS IS THE START OF THE EXECUTION
|
|
if __name__ == "__main__":
|
|
main()
|