1
0
mirror of synced 2024-11-25 18:54:29 +01:00

tweak setup.py to work with multiple top-level files.

silence lots of warnings.
add unicode box characters and --ascii, --unicode
This commit is contained in:
Tom Pusateri 2023-02-20 18:13:21 -05:00
parent 4e8326ea6c
commit 23d27c8e72
5 changed files with 492 additions and 309 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
__pycache__
protocol.egg-info

View File

@ -15,7 +15,7 @@
# -> E-Mail: luis.mgarc@gmail.com #
# -> WWWW: http://www.luismg.com #
# -> GitHub: https://github.com/luismartingarcia #
# # #
# #
################################################################################
# #
# This file is part of Protocol. #
@ -66,4 +66,3 @@ APPLICATION_AUTHOR_EMAIL="luis.mgarc@gmail.com"
# Operation return codes
OP_SUCCESS = 0 # Function performed operation successfully
OP_FAILURE = -1 # Error encountered while performing operation

275
protocol
View File

@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/env python3
################################################################################
# ____ _ _ #
# | _ \ _ __ ___ | |_ ___ ___ ___ | | #
@ -60,7 +60,11 @@ import sys
from datetime import date
# INTERNAL IMPORTS
from constants import *
from constants import (
APPLICATION_NAME, APPLICATION_VERSION,
APPLICATION_AUTHOR, APPLICATION_AUTHOR_EMAIL,
OP_FAILURE, OP_SUCCESS
)
import specs
@ -69,6 +73,7 @@ class ProtocolException(Exception):
"""
This class represents exceptions raised by the Protocol class
"""
def __init__(self, errmsg):
self.errmsg = errmsg
@ -88,18 +93,34 @@ class Protocol():
Class constructor.
@param spec is the textual specification that describes the protocol.
"""
# unicode
self.u_top_hdr_char_start = "┌" # Character for start of the top border line
self.u_top_hdr_char_end = "┐" # Character for end of the top border line
self.u_bottom_hdr_char_start = "└" # Character for start of the bottom border line
self.u_bottom_hdr_char_end = "┘" # Character for end of the bottom border line
self.u_hdr_char_start = "├" # Character for start of the border line
self.u_hdr_char_end = "┤" # Character for end of the border line
self.u_hdr_char_fill = "─" # Fill character for border positions
self.u_hdr_char_sep = "│" # Field separator character
self.u_hdr_connector_down = '┬'
self.u_hdr_connector_up = '┴'
self.u_hdr_connector_cross = '┼'
# ASCII
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.do_ascii = True # True: print ASCII box characters
self.do_unicode = False # False: print unicode box characters
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
@ -114,7 +135,9 @@ class Protocol():
fields = parts[0]
opts = parts[1]
if spec.count("?") > 1:
raise ProtocolException("FATAL: Character '?' may only be used as an option separator.")
raise ProtocolException(
"FATAL: Character '?' may only be used as an option separator."
)
else:
fields = spec
opts = None
@ -126,11 +149,15 @@ class Protocol():
text, bits = item.split(":")
bits = int(bits)
if bits <= 0:
raise ProtocolException("FATAL: Fields must be at least one bit long (%s)" %spec)
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)
raise ProtocolException(
"FATAL: Invalid field_list specification (%s)" % spec
)
self.field_list.append({"text": text, "len": bits})
# Parse options
@ -142,7 +169,9 @@ class Protocol():
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)
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
@ -151,10 +180,16 @@ class Protocol():
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"]:
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))
raise ProtocolException(
"FATAL: Invalid value for '%s' option (%s)" % (var, value)
)
else:
if var.lower() == "oddchar":
self.hdr_char_fill_odd = value
@ -173,7 +208,6 @@ class Protocol():
return self.field_list
def _get_top_numbers(self):
"""
@return a string representing the bit units and bit tens on top of the
@ -197,8 +231,7 @@ class Protocol():
result = "".join(lines)
return result if len(result) > 0 else None
def _get_horizontal(self, width=None):
def _get_horizontal(self, width=None, textline=None, fields=None, bottom=False, offset=0):
"""
@return the horizontal border line that separates field rows.
@param width controls how many field bits the line should cover. By
@ -207,23 +240,88 @@ class Protocol():
"""
if width is None:
width = self.bits_per_line
if width<=0:
elif width <= 0:
return ""
# if above first text line then at the top
if textline == 1:
top = True
else:
top = False
# first character of the line
if self.do_unicode:
if top is True or offset > 0:
a = "%s" % self.u_top_hdr_char_start
elif bottom is True:
if width < self.bits_per_line:
a = "%s" % self.u_hdr_char_start
else:
a = "%s" % self.u_bottom_hdr_char_start
else:
a = "%s" % self.u_hdr_char_start
else:
a = "%s" % self.hdr_char_start
if self.do_unicode:
# create the baseline
chars = []
for i in range(2 * (width-1)):
chars.append(self.u_hdr_char_fill)
# look at fields to determine where up or down connections are made
start_above = set()
start_below = set()
for field in fields:
if field["start"] != 0:
if field["line"] == textline:
index = 2 * field["start"] - 1
if index < len(chars):
chars[index] = self.u_hdr_connector_down
start_above.add(field["start"])
# if field["line"] == textline - 1 and field["MF"] is False:
if field["line"] == textline - 1:
index = 2 * field["start"] - 1
if index < len(chars):
chars[index] = self.u_hdr_connector_up
start_below.add(field["start"])
# look for cross connectors
if top is False and bottom is False:
positions = start_above.intersection(start_below)
for position in positions:
chars[2 * position - 1] = self.u_hdr_connector_cross
b = "".join(chars)
else:
# if ASCII, alternate +- characters between first and last
b = (self.hdr_char_fill_even+self.hdr_char_fill_odd)*(width-1)
# last character of the line
if self.do_unicode:
if top is True:
c = "%s%s" % (self.u_hdr_char_fill, self.u_top_hdr_char_end)
elif bottom is True:
c = "%s%s" % (self.u_hdr_char_fill, self.u_bottom_hdr_char_end)
else:
# if the field ends here and the next field spans, we need a corner
# TODO
c = "%s%s" % (self.u_hdr_char_fill, self.u_hdr_char_end)
else:
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
"""
if self.do_unicode:
return self.u_hdr_char_sep
else:
return self.hdr_char_sep
def _process_field_list(self):
"""
Processes the list of protocol fields that we got from the spec and turns
@ -234,33 +332,40 @@ class Protocol():
new_fields = []
bits_in_line = 0
i = 0
text_line = 1
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:
if available_in_line >= field['len']:
field['line'] = text_line
field['start'] = bits_in_line
bits_in_line += field['len']
field['end'] = bits_in_line - 1
new_fields.append(field)
bits_in_line+=field_len
i += 1
if bits_in_line == self.bits_per_line:
bits_in_line = 0
text_line += 1
# 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:
if bits_in_line == 0 and field['len'] % self.bits_per_line == 0:
field['start'] = 0
field['end'] = self.bits_per_line - 1
field['line'] = text_line
text_line += 1
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
@ -269,23 +374,31 @@ class Protocol():
# 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}
if available_in_line >= field['len']-available_in_line:
new_field = {
'text': field_text, 'len': available_in_line, "MF": True,
'start': bits_in_line, 'end': self.bits_per_line - 1,
'line': text_line,
}
new_fields.append(new_field)
field['text'] = ""
field['len']=field_len-available_in_line
field['len'] = field['len']-available_in_line
field['MF'] = False
else:
new_field = {'text':"", 'len':available_in_line, "MF":True}
new_field = {
'text': "", 'len': available_in_line, "MF": True,
'start': bits_in_line, 'end': self.bits_per_line - 1,
'line': text_line,
}
new_fields.append(new_field)
field['text'] = field_text
field['len']=field_len-available_in_line
field['len'] = field['len']-available_in_line
field['MF'] = False
bits_in_line = 0
text_line += 1
continue
return new_fields
# Convert to string
def __str__(self):
"""
@ -304,7 +417,8 @@ class Protocol():
numbers = self._get_top_numbers()
if numbers is not None:
lines.append(numbers)
lines.append(self._get_horizontal())
textline = 1
lines.append(self._get_horizontal(textline=textline, fields=proto_fields))
# Print all protocol fields
bits_in_line = 0
@ -348,6 +462,7 @@ class Protocol():
lines.append(current_line)
current_line = ""
bits_in_line = 0
textline += 1
# 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.:
@ -360,8 +475,16 @@ class Protocol():
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)
line_left = self._get_horizontal(
self.bits_per_line - field_len,
textline=textline,
fields=proto_fields,
bottom=True
)
if len(line_left) == 0:
if self.do_unicode:
line_left = self.u_hdr_char_start
else:
line_left = self.hdr_char_start
# Now print some empty space to cover the part that
@ -371,29 +494,55 @@ class Protocol():
# end our line
if proto_fields[p+1]['len'] >= self.bits_per_line:
line_center = " " * ((2*(field_len)-1))
if self.do_unicode:
line_right = self.u_hdr_char_end
else:
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'])
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'],
textline=textline,
fields=proto_fields,
offset=len(line_left) + len(line_center)
)
lines.append(line_left+line_center+line_right)
else:
lines.append(self._get_horizontal())
lines.append(
self._get_horizontal(textline=textline, fields=proto_fields)
)
else:
lines.append(self._get_horizontal())
bottom = (p == len(proto_fields) - 1)
lines.append(
self._get_horizontal(textline=textline,
fields=proto_fields,
bottom=bottom)
)
# 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))
lines.append(
self._get_horizontal(
bits_in_line,
textline=textline,
fields=proto_fields,
bottom=True
)
)
else:
# Add the separator character
if self.do_unicode:
current_line += self.u_hdr_char_sep
else:
current_line += self.hdr_char_sep
# We don't have enough space for the field on this line.
@ -414,8 +563,16 @@ class Protocol():
# Let's figure out which character we need to use
# to start and end the current line
if i % 2 == 1:
if self.do_unicode:
start_line = self.u_hdr_char_start
end_line = self.u_hdr_char_end
else:
start_line = self.hdr_char_start
end_line = self.hdr_char_end
else:
if self.do_unicode:
start_line = self.u_hdr_char_sep
end_line = self.u_hdr_char_sep
else:
start_line = self.hdr_char_sep
end_line = self.hdr_char_sep
@ -424,12 +581,19 @@ class Protocol():
# text.
if i == central_line:
lines.append(start_line + str.center(field_text, (self.bits_per_line*2)-1) + end_line)
textline += 1
# 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())
if p == len(proto_fields) - 1:
bottom = True
else:
bottom = False
lines.append(
self._get_horizontal(textline=textline, fields=proto_fields, bottom=bottom)
)
# 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
@ -463,6 +627,8 @@ class Main():
self.hdr_char_fill_even = None # Fill character for border even positions
self.hdr_char_sep = None # Field separator character
self.do_ascii = True # ASCII box characters
self.do_unicode = False # unicode box characters
def display_help(self):
"""
@ -470,7 +636,9 @@ class Main():
"""
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("Copyright (C) 2014-%i, %s (%s)." % (
date.today().year, APPLICATION_AUTHOR, APPLICATION_AUTHOR_EMAIL)
)
print("This software comes with ABSOLUTELY NO WARRANTY.")
print("")
self.display_usage()
@ -478,10 +646,12 @@ class Main():
print(" <protocol> : Name of an existing protocol")
print(" <spec> : Field by field specification of non-existing protocol")
print("OPTIONS:")
print(" -a, --ascii : Use ASCII line characters (default)")
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(" -u, --unicode : Use Unicode box characters")
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")
@ -489,21 +659,18 @@ class Main():
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
@ -514,10 +681,10 @@ class Main():
i = 0
# Read the contents of the whole file
try:
f = open(filename)
with open(filename) as f:
lines = f.readlines()
f.close()
except:
except (FileNotFoundError, PermissionError, OSError):
print("Error while reading file %s. Please make sure it exists and it's readable." % filename)
sys.exit(1)
@ -537,7 +704,6 @@ class Main():
return i
def parse_cmd_line_args(self, argv, is_config_file=False):
"""
Parses command-line arguments and stores any relevant information
@ -545,7 +711,7 @@ class Main():
"""
# Store a reference to command line args for later use.
if is_config_file==False:
if is_config_file is False:
self.cmd_line_args = argv
# Check we have received enough command-line parameters
@ -558,7 +724,7 @@ class Main():
# Useful for args like -c <filename>. This avoids parsing the
# filename as it if was a command-line flag.
if skip_arg==True:
if skip_arg is True:
skip_arg = False
continue
@ -591,6 +757,16 @@ class Main():
elif argv[i] == "-n" or argv[i] == "--no-numbers":
self.skip_numbers = True
# Use ASCII line characters
elif argv[i] == "-a" or argv[i] == "--ascii":
self.do_ascii = True
self.do_unicode = False
# Use Unicode box characters
elif argv[i] == "-u" or argv[i] == "--unicode":
self.do_ascii = False
self.do_unicode = True
# Character variations
elif argv[i] in ["--oddchar", "--evenchar", "--startchar", "--endchar", "--sepchar"]:
# Make sure we have an actual parameter after the flag
@ -653,7 +829,7 @@ class Main():
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]);
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])
@ -677,8 +853,6 @@ class Main():
return OP_SUCCESS, None
def run(self):
"""
This is Protocol's 'core' method: parses command line argument and prints
@ -705,6 +879,11 @@ class Main():
else:
self.protocols[i].do_print_top_tens = True
self.protocols[i].do_print_top_units = True
if self.do_unicode is True:
self.protocols[i].do_unicode = True
self.protocols[i].do_ascii = False
# override ASCII default characters, can't override unicode characters
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:

View File

@ -55,8 +55,11 @@
# #
################################################################################
from distutils.core import setup, Extension
setup(name='protocol',
from distutils.core import setup
setup(
name='protocol',
version='0.1',
scripts=['protocol', 'constants.py', 'specs.py']
scripts=['protocol'],
py_modules=[]
)

View File

@ -724,7 +724,8 @@ Field_124:124,Field_127:127"
# Dictionary of specs
protocols={"ethernet":ethernet,
protocols = {
"ethernet": ethernet,
"8021q": dot1q,
"dot1q": dot1q,
"tcp": tcp,
@ -765,4 +766,3 @@ protocols={"ethernet":ethernet,
"example": example,
"test": test
}