1
0
mirror of https://github.com/craigerl/aprsd.git synced 2024-11-21 23:55:17 -05:00

Added tox support

This patch adds support for tox.  Tox is used to run various
python compliance tests.  This enables pep8 tests, as well as python2
and python3 compatibility as well as coverage and documentation
building.
This commit is contained in:
Walter A. Boring IV 2019-05-15 20:12:59 +00:00
parent 28e062bd44
commit d1a50c6559
6 changed files with 364 additions and 220 deletions

View File

@ -17,6 +17,7 @@
import sys import sys
import time import time
def fuzzy(hour, minute, degree=1): def fuzzy(hour, minute, degree=1):
'''Implements the fuzzy clock. '''Implements the fuzzy clock.
returns the the string that spells out the time - hour:minute returns the the string that spells out the time - hour:minute
@ -25,7 +26,7 @@ def fuzzy(hour, minute, degree=1):
When degree = 2, time is in quantum of 15 minutes.''' When degree = 2, time is in quantum of 15 minutes.'''
if degree <= 0 or degree > 2: if degree <= 0 or degree > 2:
print 'Please use a degree of 1 or 2. Using fuzziness degree=1' print('Please use a degree of 1 or 2. Using fuzziness degree=1')
degree = 1 degree = 1
begin = 'It\'s ' begin = 'It\'s '
@ -37,7 +38,8 @@ def fuzzy(hour, minute, degree=1):
b0 = ' past ' b0 = ' past '
b1 = ' to ' b1 = ' to '
hourList = ('One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten', 'Eleven', 'Twelve') hourList = ('One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight',
'Nine', 'Ten', 'Eleven', 'Twelve')
s1 = s2 = s3 = s4 = '' s1 = s2 = s3 = s4 = ''
base = 5 base = 5
@ -49,7 +51,8 @@ def fuzzy(hour, minute, degree=1):
base = 15 base = 15
val = ('Quarter', 'Half') val = ('Quarter', 'Half')
dmin = minute % base # to find whether we have to use 'almost', 'exactly' or 'around' # to find whether we have to use 'almost', 'exactly' or 'around'
dmin = minute % base
if minute > 30: if minute > 30:
pos = int((60 - minute) / base) # position in the tuple 'val' pos = int((60 - minute) / base) # position in the tuple 'val'
else: else:
@ -69,13 +72,16 @@ def fuzzy(hour, minute, degree=1):
s2 = val[pos] s2 = val[pos]
if minute <= base/2: # Case like "It's around/exactly Ten" if minute <= base / 2:
# Case like "It's around/exactly Ten"
s2 = s3 = '' s2 = s3 = ''
s4 = hourList[hour - 12 - 1] s4 = hourList[hour - 12 - 1]
elif minute >= 60-base/2: # Case like "It's almost Ten" elif minute >= 60 - base / 2:
# Case like "It's almost Ten"
s2 = s3 = '' s2 = s3 = ''
s4 = hourList[hour - 12] s4 = hourList[hour - 12]
else: # Other cases with all words, like "It's around Quarter past One" else:
# Other cases with all words, like "It's around Quarter past One"
if minute > 30: if minute > 30:
s3 = b1 # to s3 = b1 # to
s4 = hourList[hour - 12] s4 = hourList[hour - 12]
@ -85,6 +91,7 @@ def fuzzy(hour, minute, degree=1):
return begin + s1 + s2 + s3 + s4 return begin + s1 + s2 + s3 + s4
def main(): def main():
deg = 1 deg = 1
stm = time.localtime() stm = time.localtime()
@ -94,8 +101,8 @@ def main():
if len(sys.argv) >= 2: if len(sys.argv) >= 2:
try: try:
deg = int(sys.argv[1]) deg = int(sys.argv[1])
except: except Exception:
print 'Please use a degree of 1 or 2. Using fuzziness degree=1' print('Please use a degree of 1 or 2. Using fuzziness degree=1')
if len(sys.argv) >= 3: if len(sys.argv) >= 3:
tm = sys.argv[2].split(':') tm = sys.argv[2].split(':')
@ -104,11 +111,11 @@ def main():
m = int(tm[1]) m = int(tm[1])
if h < 0 or h > 23 or m < 0 or m > 59: if h < 0 or h > 23 or m < 0 or m > 59:
raise Exception raise Exception
except: except Exception:
print 'Bad time entered. Using the system time.' print('Bad time entered. Using the system time.')
h = stm.tm_hour h = stm.tm_hour
m = stm.tm_min m = stm.tm_min
print fuzzy(h, m, deg) print(fuzzy(h, m, deg))
return return
main() main()

View File

@ -46,7 +46,7 @@ from imapclient import IMAPClient, SEEN
# local imports here # local imports here
from aprsd.fuzzyclock import fuzzy from aprsd.fuzzyclock import fuzzy
import utils from aprsd import utils
# setup the global logger # setup the global logger
LOG = logging.getLogger('APRSD') LOG = logging.getLogger('APRSD')
@ -58,7 +58,7 @@ CONFIG = None
# HOST = "noam.aprs2.net" # north america tier2 servers round robin # HOST = "noam.aprs2.net" # north america tier2 servers round robin
# USER = "KM6XXX-9" # callsign of this aprs client with SSID # USER = "KM6XXX-9" # callsign of this aprs client with SSID
# PASS = "99999" # google how to generate this # PASS = "99999" # google how to generate this
# BASECALLSIGN = "KM6XXX" # callsign of radio in the field to which we send email # BASECALLSIGN = "KM6XXX" # callsign of radio in the field to send email
# shortcuts = { # shortcuts = {
# "aa" : "5551239999@vtext.com", # "aa" : "5551239999@vtext.com",
# "cl" : "craiglamparter@somedomain.org", # "cl" : "craiglamparter@somedomain.org",
@ -66,9 +66,18 @@ CONFIG = None
# } # }
# globals - tell me a better way to update data being used by threads # globals - tell me a better way to update data being used by threads
email_sent_dict = {} # message_number:time combos so we don't resend the same email in five mins {int:int}
ack_dict = {} # message_nubmer:ack combos so we stop sending a message after an ack from radio {int:int} # message_number:time combos so we don't resend the same email in
message_number = 0 # current aprs radio message number, increments for each message we send over rf {int} # five mins {int:int}
email_sent_dict = {}
# message_nubmer:ack combos so we stop sending a message after an
# ack from radio {int:int}
ack_dict = {}
# current aprs radio message number, increments for each message we
# send over rf {int}
message_number = 0
# global telnet connection object # global telnet connection object
tn = None tn = None
@ -93,7 +102,7 @@ def setup_connection():
LOG.debug("Setting up telnet connection to '%s:%s'" % (host, port)) LOG.debug("Setting up telnet connection to '%s:%s'" % (host, port))
try: try:
tn = telnetlib.Telnet(host, port) tn = telnetlib.Telnet(host, port)
except Exception, e: except Exception:
LOG.exception("Telnet session failed.") LOG.exception("Telnet session failed.")
sys.exit(-1) sys.exit(-1)
@ -103,12 +112,15 @@ def signal_handler(signal, frame):
# sys.exit(0) # thread ignores this # sys.exit(0) # thread ignores this
os._exit(0) os._exit(0)
### end signal_handler # end signal_handler
def parse_email(msgid, data, server): def parse_email(msgid, data, server):
envelope = data[b'ENVELOPE'] envelope = data[b'ENVELOPE']
#print('ID:%d "%s" (%s)' % (msgid, envelope.subject.decode(), envelope.date )) # print('ID:%d "%s" (%s)' %
f = re.search('([\.\w_-]+@[\.\w_-]+)', str(envelope.from_[0]) ) # email address match # (msgid, envelope.subject.decode(), envelope.date))
# email address match
f = re.search('([\.\w_-]+@[\.\w_-]+)', str(envelope.from_[0]))
if f is not None: if f is not None:
from_addr = f.group(1) from_addr = f.group(1)
else: else:
@ -118,7 +130,9 @@ def parse_email(msgid, data, server):
if msg.is_multipart(): if msg.is_multipart():
text = "" text = ""
html = None html = None
body = "* unreadable msg received" # default in case body somehow isn't set below - happened once
# default in case body somehow isn't set below - happened once
body = "* unreadable msg received"
for part in msg.get_payload(): for part in msg.get_payload():
if part.get_content_charset() is None: if part.get_content_charset() is None:
# We cannot know the character set, so return decoded "something" # We cannot know the character set, so return decoded "something"
@ -128,23 +142,30 @@ def parse_email(msgid, data, server):
charset = part.get_content_charset() charset = part.get_content_charset()
if part.get_content_type() == 'text/plain': if part.get_content_type() == 'text/plain':
text = unicode(part.get_payload(decode=True), str(charset), "ignore").encode('utf8', 'replace') text = unicode(part.get_payload(decode=True), str(charset),
"ignore").encode('utf8', 'replace')
if part.get_content_type() == 'text/html': if part.get_content_type() == 'text/html':
html = unicode(part.get_payload(decode=True), str(charset), "ignore").encode('utf8', 'replace') html = unicode(part.get_payload(decode=True), str(charset),
"ignore").encode('utf8', 'replace')
if text is not None: if text is not None:
body = text.strip() # strip removes white space fore and aft of string # strip removes white space fore and aft of string
body = text.strip()
else: else:
body = html.strip() body = html.strip()
else: else:
text = unicode(msg.get_payload(decode=True), msg.get_content_charset(), 'ignore').encode('utf8', 'replace') text = unicode(msg.get_payload(decode=True), msg.get_content_charset(),
'ignore').encode('utf8', 'replace')
body = text.strip() body = text.strip()
body = re.sub('<[^<]+?>', '', body) # strip all html tags # strip all html tags
body = body.replace("\n", " ").replace("\r", " ") # strip CR/LF, make it one line, .rstrip fails at this body = re.sub('<[^<]+?>', '', body)
# strip CR/LF, make it one line, .rstrip fails at this
body = body.replace("\n", " ").replace("\r", " ")
return(body, from_addr) return(body, from_addr)
## end parse_email # end parse_email
def resend_email(count): def resend_email(count):
@ -152,10 +173,11 @@ def resend_email(count):
month = date.strftime("%B")[:3] # Nov, Mar, Apr month = date.strftime("%B")[:3] # Nov, Mar, Apr
day = date.day day = date.day
year = date.year year = date.year
today = str(day) + "-" + month + "-" + str(year) today = "%s-%s-%s" % (day, month, year)
shortcuts = CONFIG['shortcuts'] shortcuts = CONFIG['shortcuts']
shortcuts_inverted = dict([[v,k] for k,v in shortcuts.items()]) # swap key/value # swap key/value
shortcuts_inverted = dict([[v, k] for k, v in shortcuts.items()])
LOG.debug("resend_email: Connect to IMAP host '%s' with user '%s'" % LOG.debug("resend_email: Connect to IMAP host '%s' with user '%s'" %
(CONFIG['imap']['host'], (CONFIG['imap']['host'],
@ -172,12 +194,16 @@ def resend_email(count):
messages.sort(reverse=True) messages.sort(reverse=True)
del messages[int(count):] # only the latest "count" messages del messages[int(count):] # only the latest "count" messages
for message in messages: for message in messages:
for msgid, data in list(server.fetch(message, ['ENVELOPE']).items()): # one at a time, otherwise order is random for msgid, data in list(server.fetch(message, ['ENVELOPE']).items()):
# one at a time, otherwise order is random
(body, from_addr) = parse_email(msgid, data, server) (body, from_addr) = parse_email(msgid, data, server)
server.remove_flags(msgid, [SEEN]) # unset seen flag, will stay bold in email client # unset seen flag, will stay bold in email client
if from_addr in shortcuts_inverted: # reverse lookup of a shortcut server.remove_flags(msgid, [SEEN])
if from_addr in shortcuts_inverted:
# reverse lookup of a shortcut
from_addr = shortcuts_inverted[from_addr] from_addr = shortcuts_inverted[from_addr]
reply = "-" + from_addr + " * " + body # asterisk indicates a resend # asterisk indicates a resend
reply = "-" + from_addr + " * " + body
send_message(fromcall, reply) send_message(fromcall, reply)
msgexists = True msgexists = True
@ -186,29 +212,36 @@ def resend_email(count):
h = stm.tm_hour h = stm.tm_hour
m = stm.tm_min m = stm.tm_min
s = stm.tm_sec s = stm.tm_sec
# append time as a kind of serial number to prevent FT1XDR from thinking this is a duplicate message. # append time as a kind of serial number to prevent FT1XDR from
# The FT1XDR pretty much ignores the aprs message number in this regard. The FTM400 gets it right. # thinking this is a duplicate message.
reply = "No new msg " + str(h).zfill(2) + ":" + str(m).zfill(2) + ":" + str(s).zfill(2) # The FT1XDR pretty much ignores the aprs message number in this
# regard. The FTM400 gets it right.
reply = "No new msg %s:%s:%s" % (str(h).zfill(2),
str(m).zfill(2),
str(s).zfill(2))
send_message(fromcall, reply) send_message(fromcall, reply)
server.logout() server.logout()
### end resend_email() # end resend_email()
def check_email_thread(): def check_email_thread():
# print "Email thread disabled." # print "Email thread disabled."
# return # return
LOG.debug("Starting Email thread") LOG.debug("Starting Email thread")
threading.Timer(55, check_email_thread).start() # how do we skip first run? # how do we skip first run?
threading.Timer(55, check_email_thread).start()
shortcuts = CONFIG['shortcuts'] shortcuts = CONFIG['shortcuts']
shortcuts_inverted = dict([[v,k] for k,v in shortcuts.items()]) # swap key/value # swap key/value
shortcuts_inverted = dict([[v, k] for k, v in shortcuts.items()])
date = datetime.datetime.now() date = datetime.datetime.now()
month = date.strftime("%B")[:3] # Nov, Mar, Apr month = date.strftime("%B")[:3] # Nov, Mar, Apr
day = date.day day = date.day
year = date.year year = date.year
today = str(day) + "-" + month + "-" + str(year) today = "%s-%s-%s" % (day, month, year)
LOG.debug("Connect to IMAP host '%s' with user '%s'" % LOG.debug("Connect to IMAP host '%s' with user '%s'" %
(CONFIG['imap']['host'], (CONFIG['imap']['host'],
@ -238,44 +271,53 @@ def check_email_thread():
else: else:
from_addr = "noaddr" from_addr = "noaddr"
if "APRS" not in server.get_flags(msgid)[msgid]: #if msg not flagged as sent via aprs if "APRS" not in server.get_flags(msgid)[msgid]:
m = server.fetch([msgid], ['RFC822']) # if msg not flagged as sent via aprs
server.fetch([msgid], ['RFC822'])
(body, from_addr) = parse_email(msgid, data, server) (body, from_addr) = parse_email(msgid, data, server)
server.remove_flags(msgid, [SEEN]) # unset seen flag, will stay bold in email client # unset seen flag, will stay bold in email client
server.remove_flags(msgid, [SEEN])
if from_addr in shortcuts_inverted: # reverse lookup of a shortcut if from_addr in shortcuts_inverted:
# reverse lookup of a shortcut
from_addr = shortcuts_inverted[from_addr] from_addr = shortcuts_inverted[from_addr]
reply = "-" + from_addr + " " + body reply = "-" + from_addr + " " + body
# print "Sending message via aprs: " + reply # print "Sending message via aprs: " + reply
send_message(CONFIG['ham']['callsign'], reply) #radio # radio
server.add_flags(msgid, ['APRS']) #flag message as sent via aprs send_message(CONFIG['ham']['callsign'], reply)
server.remove_flags(msgid, [SEEN]) #unset seen flag, will stay bold in email client # flag message as sent via aprs
server.add_flags(msgid, ['APRS'])
# unset seen flag, will stay bold in email client
server.remove_flags(msgid, [SEEN])
server.logout() server.logout()
### end check_email() # end check_email()
def send_ack_thread(tocall, ack, retry_count): def send_ack_thread(tocall, ack, retry_count):
tocall = tocall.ljust(9) # pad to nine chars tocall = tocall.ljust(9) # pad to nine chars
line = CONFIG['aprs']['login'] + ">APRS::" + tocall + ":ack" + str(ack) + "\n" line = "%s>APRS::%s:ack%s\n" % (CONFIG['aprs']['login'], tocall, ack)
for i in range(retry_count, 0, -1): for i in range(retry_count, 0, -1):
LOG.info("Sending ack __________________ Tx(" + str(i) + ")") LOG.info("Sending ack __________________ Tx(%s)" % i)
LOG.info("Raw : " + line) LOG.info("Raw : %s" % line)
LOG.info("To : " + tocall) LOG.info("To : %s" % tocall)
LOG.info("Ack number : " + str(ack)) LOG.info("Ack number : %s" % ack)
tn.write(line) tn.write(line)
time.sleep(31) # aprs duplicate detection is 30 secs? (21 only sends first, 28 skips middle) # aprs duplicate detection is 30 secs?
# (21 only sends first, 28 skips middle)
time.sleep(31)
return() return()
### end_send_ack_thread # end_send_ack_thread
def send_ack(tocall, ack): def send_ack(tocall, ack):
retry_count = 3 retry_count = 3
thread = threading.Thread(target = send_ack_thread, args = (tocall, ack, retry_count)) thread = threading.Thread(target=send_ack_thread,
args=(tocall, ack, retry_count))
thread.start() thread.start()
return() return()
### end send_ack() # end send_ack()
def send_message_thread(tocall, message, this_message_number, retry_count): def send_message_thread(tocall, message, this_message_number, retry_count):
@ -292,12 +334,13 @@ def send_message_thread(tocall, message, this_message_number, retry_count):
LOG.info("To : " + tocall) LOG.info("To : " + tocall)
LOG.info("Message : " + message) LOG.info("Message : " + message)
tn.write(line) tn.write(line)
sleeptime = (retry_count - i + 1) * 31 # decaying repeats, 31 to 93 second intervals # decaying repeats, 31 to 93 second intervals
sleeptime = (retry_count - i + 1) * 31
time.sleep(sleeptime) time.sleep(sleeptime)
else: else:
break break
return return
### end send_message_thread # end send_message_thread
def send_message(tocall, message): def send_message(tocall, message):
@ -307,39 +350,50 @@ def send_message(tocall, message):
if message_number > 98: # global if message_number > 98: # global
message_number = 0 message_number = 0
message_number += 1 message_number += 1
if len(ack_dict) > 90: # empty ack dict if it's really big, could result in key error later if len(ack_dict) > 90:
LOG.debug("DEBUG: Length of ack dictionary is big at " + str(len(ack_dict)) + " clearing.") # empty ack dict if it's really big, could result in key error later
LOG.debug("DEBUG: Length of ack dictionary is big at %s clearing." %
len(ack_dict))
ack_dict.clear() ack_dict.clear()
LOG.debug(pprint.pformat(ack_dict)) LOG.debug(pprint.pformat(ack_dict))
LOG.debug("DEBUG: Cleared ack dictionary, ack_dict length is now " + str(len(ack_dict)) + ".") LOG.debug("DEBUG: Cleared ack dictionary, ack_dict length is now %s." %
len(ack_dict))
ack_dict[message_number] = 0 # clear ack for this message number ack_dict[message_number] = 0 # clear ack for this message number
tocall = tocall.ljust(9) # pad to nine chars tocall = tocall.ljust(9) # pad to nine chars
message = message[:67] # max? ftm400 displays 64, raw msg shows 74
# max? ftm400 displays 64, raw msg shows 74
# and ftm400-send is max 64. setting this to # and ftm400-send is max 64. setting this to
# 67 displays 64 on the ftm400. (+3 {01 suffix) # 67 displays 64 on the ftm400. (+3 {01 suffix)
# feature req: break long ones into two msgs # feature req: break long ones into two msgs
message = message[:67]
thread = threading.Thread( thread = threading.Thread(
target=send_message_thread, target=send_message_thread,
args=(tocall, message, message_number, retry_count)) args=(tocall, message, message_number, retry_count))
thread.start() thread.start()
return() return()
### end send_message() # end send_message()
def process_message(line): def process_message(line):
f = re.search('^(.*)>', line) f = re.search('^(.*)>', line)
fromcall = f.group(1) fromcall = f.group(1)
searchstring = '::' + CONFIG['aprs']['login'] + '[ ]*:(.*)' # verify this, callsign is padded out with spaces to colon searchstring = '::%s[ ]*:(.*)' % CONFIG['aprs']['login']
# verify this, callsign is padded out with spaces to colon
m = re.search(searchstring, line) m = re.search(searchstring, line)
fullmessage = m.group(1) fullmessage = m.group(1)
ack_attached = re.search('(.*){([0-9A-Z]+)', fullmessage) # ack formats include: {1, {AB}, {12 ack_attached = re.search('(.*){([0-9A-Z]+)', fullmessage)
if ack_attached: # "{##" suffix means radio wants an ack back # ack formats include: {1, {AB}, {12
message = ack_attached.group(1) # message content if ack_attached:
ack_num = ack_attached.group(2) # suffix number to use in ack # "{##" suffix means radio wants an ack back
# message content
message = ack_attached.group(1)
# suffix number to use in ack
ack_num = ack_attached.group(2)
else: else:
message = fullmessage message = fullmessage
ack_num = "0" # ack not requested, but lets send one as 0 # ack not requested, but lets send one as 0
ack_num = "0"
LOG.info("Received message______________") LOG.info("Received message______________")
LOG.info("Raw : " + line) LOG.info("Raw : " + line)
@ -348,7 +402,7 @@ def process_message(line):
LOG.info("Msg number : " + str(ack_num)) LOG.info("Msg number : " + str(ack_num))
return (fromcall, message, ack_num) return (fromcall, message, ack_num)
### end process_message() # end process_message()
def send_email(to_addr, content): def send_email(to_addr, content):
@ -379,7 +433,7 @@ def send_email(to_addr, content):
return(-1) return(-1)
s.quit() s.quit()
return(0) return(0)
### end send_email # end send_email
# Setup the logging faciility # Setup the logging faciility
@ -413,7 +467,7 @@ def setup_logging(args):
LOG.addHandler(sh) LOG.addHandler(sh)
### main() ### # main() ###
def main(args=args): def main(args=args):
global CONFIG global CONFIG
@ -442,7 +496,7 @@ def main(args=args):
line = line + char line = line + char
line = line.replace('\n', '') line = line.replace('\n', '')
LOG.info(line) LOG.info(line)
searchstring = '::' + user searchstring = '::%s' % user
# is aprs message to us, not beacon, status, etc # is aprs message to us, not beacon, status, etc
if re.search(searchstring, line): if re.search(searchstring, line):
(fromcall, message, ack) = process_message(line) (fromcall, message, ack) = process_message(line)
@ -547,32 +601,47 @@ def main(args=args):
# WEATHER (w) "42F(68F/48F) Haze. Tonight, Haze then Chance Rain." # WEATHER (w) "42F(68F/48F) Haze. Tonight, Haze then Chance Rain."
elif re.search('^w', message): elif re.search('^w', message):
# get my last location from aprsis then get weather from weather service # get my last location from aprsis then get weather from
# weather service
try: try:
url = "http://api.aprs.fi/api/get?name=" + fromcall + "&what=loc&apikey=104070.f9lE8qg34L8MZF&format=json" url = ("http://api.aprs.fi/api/get?"
"&what=loc&apikey=104070.f9lE8qg34L8MZF&format=json"
"&name=%s" % fromcall)
response = urllib.urlopen(url) response = urllib.urlopen(url)
aprs_data = json.loads(response.read()) aprs_data = json.loads(response.read())
lat = aprs_data['entries'][0]['lat'] lat = aprs_data['entries'][0]['lat']
lon = aprs_data['entries'][0]['lng'] lon = aprs_data['entries'][0]['lng']
url2 = "https://forecast.weather.gov/MapClick.php?lat=" + str(lat) + "&lon=" + str(lon) + "&FcstType=json" url2 = ("https://forecast.weather.gov/MapClick.php?lat=%s"
"&lon=%s&FcstType=json" % (lat, lon))
response2 = urllib.urlopen(url2) response2 = urllib.urlopen(url2)
wx_data = json.loads(response2.read()) wx_data = json.loads(response2.read())
reply = wx_data['currentobservation']['Temp'] + "F(" + wx_data['data']['temperature'][0] + "F/" + wx_data['data']['temperature'][1] + "F) " + wx_data['data']['weather'][0] + ". " + wx_data['time']['startPeriodName'][1] + ", " + wx_data['data']['weather'][1] + "." reply = "%sF(%sF/%sF) %s. %s, %s." % (
reply = reply.encode('ascii',errors='ignore') # unicode to ascii wx_data['currentobservation']['Temp'],
wx_data['data']['temperature'][0],
wx_data['data']['temperature'][1],
wx_data['data']['weather'][0],
wx_data['time']['startPeriodName'][1],
wx_data['data']['weather'][1])
# unicode to ascii
reply = reply.encode('ascii', errors='ignore')
send_message(fromcall, reply.rstrip()) send_message(fromcall, reply.rstrip())
except: except Exception:
reply = "Unable to find you (send beacon?)" reply = "Unable to find you (send beacon?)"
send_message(fromcall, reply) send_message(fromcall, reply)
# USAGE # USAGE
else: else:
reply = "usage: time, fortune, loc, weath, -emailaddr emailbody, -#(resend)" reply = ("usage: time, fortune, loc, weath, -emailaddr "
"emailbody, -#(resend)")
send_message(fromcall, reply) send_message(fromcall, reply)
time.sleep(1) # let any threads do their thing, then ack # let any threads do their thing, then ack
send_ack(fromcall, ack) # send an ack last time.sleep(1)
# send an ack last
send_ack(fromcall, ack)
except Exception, e: except Exception as e:
LOG.error("Error in mainline loop:") LOG.error("Error in mainline loop:")
LOG.error("%s" % str(e)) LOG.error("%s" % str(e))
LOG.error("Exiting.") LOG.error("Exiting.")

View File

@ -36,6 +36,7 @@ imap:
log = logging.getLogger('APRSD') log = logging.getLogger('APRSD')
def env(*vars, **kwargs): def env(*vars, **kwargs):
"""This returns the first environment variable set. """This returns the first environment variable set.
if none are non-empty, defaults to '' or keyword arg default if none are non-empty, defaults to '' or keyword arg default
@ -55,10 +56,12 @@ def get_config():
config = yaml.load(stream) config = yaml.load(stream)
return config return config
else: else:
log.critical("%s is missing, please create a config file" % config_file) log.critical("%s is missing, please create config file" % config_file)
print("\nCopy to ~/.aprsd/config.yml and edit\n\nSample config:\n %s" % example_config) print("\nCopy to ~/.aprsd/config.yml and edit\n\nSample config:\n %s"
% example_config)
sys.exit(-1) sys.exit(-1)
# This method tries to parse the config yaml file # This method tries to parse the config yaml file
# and consume the settings. # and consume the settings.
# If the required params don't exist, # If the required params don't exist,

1
test-requirements.txt Normal file
View File

@ -0,0 +1 @@
flake8

25
tools/fast8.sh Executable file
View File

@ -0,0 +1,25 @@
#!/bin/bash
NUM_COMMITS=${FAST8_NUM_COMMITS:-1}
if [[ $NUM_COMMITS = "smart" ]]; then
# Run on all commits not submitted yet
# (sort of -- only checks vs. "master" since this is easy)
NUM_COMMITS=$(git cherry master | wc -l)
fi
echo "Checking last $NUM_COMMITS commits."
cd $(dirname "$0")/..
CHANGED=$(git diff --name-only HEAD~${NUM_COMMITS} | tr '\n' ' ')
# Skip files that don't exist
# (have been git rm'd)
CHECK=""
for FILE in $CHANGED; do
if [ -f "$FILE" ]; then
CHECK="$CHECK $FILE"
fi
done
diff -u --from-file /dev/null $CHECK | flake8 --diff

39
tox.ini Normal file
View File

@ -0,0 +1,39 @@
[tox]
minversion = 1.6
skipdist = True
envlist = py27,py36,py37,fast8,pep8,cover,docs
[testenv]
setenv = VIRTUAL_ENV={envdir}
usedevelop = True
install_command = pip install {opts} {packages}
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands =
pytest {posargs}
[testenv:cover]
commands =
pytest --cov=aprsd
[testenv:docs]
deps = -r{toxinidir}/test-requirements.txt
commands = sphinx-build -b html docs/source docs/html
[testenv:pep8]
commands =
flake8 {posargs} aprsd test
[testenv:fast8]
basepython = python3
# Use same environment directory as pep8 env to save space and install time
envdir = {toxworkdir}/pep8
commands =
{toxinidir}/tools/fast8.sh
passenv = FAST8_NUM_COMMITS
[flake8]
show-source = True
ignore = E713
exclude = .venv,.git,.tox,dist,doc,.ropeproject