From b0f60af38d5a4758cb0c1ae32a90463d586dcfcc Mon Sep 17 00:00:00 2001 From: Jonathan Harris Date: Sat, 7 Nov 2015 05:05:25 +0000 Subject: [PATCH] Add command-line program. --- EDMC.py | 114 ++++++++++++++++++++++++++++++++++++++++++ EDMarketConnector.wxs | 4 ++ README.md | 28 +++++++++++ bpc.py | 5 +- companion.py | 32 +----------- config.py | 1 + coriolis.py | 13 +++-- eddn.py | 2 +- flightlog.py | 3 +- l10n.py | 6 ++- loadout.py | 9 +++- outfitting.py | 29 ++++++++++- setup.py | 8 +++ 13 files changed, 211 insertions(+), 43 deletions(-) create mode 100755 EDMC.py diff --git a/EDMC.py b/EDMC.py new file mode 100755 index 00000000..2cc276c6 --- /dev/null +++ b/EDMC.py @@ -0,0 +1,114 @@ +#!/usr/bin/python +# +# Command-line interface. Requires prior setup through the GUI. +# + +import argparse +import sys +from time import time, sleep + +import l10n +import companion +import bpc +import outfitting +import loadout +import coriolis +import shipyard +import eddb +import prefs +from config import appcmdname, appversion, config + + +l10n.Translations().install_dummy() +EDDB = eddb.EDDB() + +SERVER_RETRY = 5 # retry pause for Companion servers [s] +EXIT_SUCCESS, EXIT_SERVER, EXIT_CREDENTIALS, EXIT_VERIFICATION, EXIT_NOT_DOCKED, EXIT_SYS_ERR = range(6) + +try: + # arg parsing + parser = argparse.ArgumentParser(prog=appcmdname, description='Prints the current system and station (if docked) to stdout and optionally writes ship loadout and/or station data to file. Requires prior setup through the accompanying GUI app.') + parser.add_argument('-v', '--version', help='print program version and exit', action='store_const', const=True) + parser.add_argument('-c', metavar='FILE', help='write ship loadout to FILE in Coriolis json format') + parser.add_argument('-e', metavar='FILE', help='write ship loadout to FILE in E:D Shipyard format') + parser.add_argument('-m', metavar='FILE', help='write station commodity market data to FILE in CSV format') + parser.add_argument('-o', metavar='FILE', help='write station outfitting data to FILE in CSV format') + parser.add_argument('-s', metavar='FILE', help='write station shipyard data to FILE in CSV format') + args = parser.parse_args() + if args.version: + print '%.2f' % (float(''.join(appversion.split('.')[:3])) / 100) # just first three digits + sys.exit(EXIT_SUCCESS) + + session = companion.Session() + session.login(config.get('username'), config.get('password')) + + querytime = int(time()) + data = session.query() + config.set('querytime', querytime) + + # Validation + if not data.get('commander') or not data['commander'].get('name','').strip(): + sys.stderr.write('Who are you?!\n') + sys.exit(EXIT_SERVER) + elif not data.get('lastSystem') or not data['lastSystem'].get('name','').strip() or not data.get('lastStarport') or not data['lastStarport'].get('name','').strip(): + sys.stderr.write('Where are you?!\n') # Shouldn't happen + sys.exit(EXIT_SERVER) + elif not data.get('ship') or not data['ship'].get('modules') or not data['ship'].get('name','').strip(): + sys.stderr.write('What are you flying?!\n') # Shouldn't happen + sys.exit(EXIT_SERVER) + elif (args.m or args.o or args.s) and not data['commander'].get('docked'): + print data['lastSystem']['name'] + sys.stderr.write("You're not docked at a station!\n") + sys.exit(EXIT_NOT_DOCKED) + + # stuff we can do when not docked + if args.c: + coriolis.export(data, args.c) + if args.e: + loadout.export(data, args.e) + + # Finally - the data looks sane and we're docked at a station + print '%s,%s' % (data['lastSystem']['name'], data['lastStarport']['name']) + (station_id, has_shipyard, has_outfitting) = EDDB.station(data['lastSystem']['name'], data['lastStarport']['name']) + + if not data['lastStarport'].get('commodities') and not has_outfitting and not has_shipyard: + sys.stderr.write("Station doesn't have anything!\n") + sys.exit(EXIT_SUCCESS) + + if args.m: + if data['lastStarport'].get('commodities'): + # Fixup anomalies in the commodity data + session.fixup(data['lastStarport']['commodities']) + bpc.export(data, True, args.m) + else: + sys.stderr.write("Station doesn't have a market\n") + + if args.o: + if has_outfitting: + outfitting.export(data, args.o) + else: + sys.stderr.write("Station doesn't supply outfitting\n") + + if args.s: + if has_shipyard: + if not data['lastStarport'].get('ships'): + sleep(SERVER_RETRY) + data = session.query() + if data['lastStarport'].get('ships'): + shipyard.export(data, args.s) + else: + sys.stderr.write("Couldn't retrieve shipyard info\n") + else: + sys.stderr.write("Station doesn't have a shipyard\n") + + sys.exit(EXIT_SUCCESS) + +except companion.ServerError as e: + sys.stderr.write('Server is down\n') + sys.exit(EXIT_SERVER_DOWN) +except companion.CredentialsError as e: + sys.stderr.write('Invalid Credentials\n') + sys.exit(EXIT_CREDENTIALS) +except companion.VerificationRequired: + sys.stderr.write('Verification Required\n') + sys.exit(EXIT_VERIFICATION) diff --git a/EDMarketConnector.wxs b/EDMarketConnector.wxs index 853150ae..0e4116da 100644 --- a/EDMarketConnector.wxs +++ b/EDMarketConnector.wxs @@ -84,6 +84,9 @@ + + + @@ -359,6 +362,7 @@ + diff --git a/README.md b/README.md index 6c364530..7717d7ae 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,34 @@ Linux: * Requires the Python “imaging-tk”, “iniparse” and “requests” modules. On Debian-based systems install these with `sudo apt-get install python-imaging-tk python-iniparse python-requests` . * Run with `./EDMarketConnector.py` . +Command-line +-------- + +The command-line program `EDMC.py` writes the current system and station (if docked) to stdout and optionally +writes ship loadout and/or station data to file. This program requires that the user has performed [setup](#setup) and verification through the app. + +Arguments: + +``` + -h, --help show this help message and exit + -v, --version print program version and exit + -c FILE write ship loadout to FILE in Coriolis json format + -e FILE write ship loadout to FILE in E:D Shipyard format + -m FILE write station commodity market data to FILE in CSV format + -o FILE write station outfitting data to FILE in CSV format + -s FILE write station shipyard data to FILE in CSV format +``` + +The program returns one of the following exit codes. Further information may be written to stderr. +
    +
  1. Success. Note that this doesn't necesssarily mean that any requested output files have been produced - for example if the current station doesn't support the facilities for which data was requested.
  2. +
  3. Server is down.
  4. +
  5. Invalid Credentials.
  6. +
  7. Verification Required.
  8. +
  9. Not docked. You have requested station data but the user is not docked at a station.
  10. +
  11. I/O or other OS error.
  12. +
+ Packaging for distribution -------- diff --git a/bpc.py b/bpc.py index c1262b02..c109e518 100644 --- a/bpc.py +++ b/bpc.py @@ -14,11 +14,12 @@ bracketmap = { 0: '', 2: 'Med', 3: 'High', } -def export(data, csv=False): +def export(data, csv=False, filename=None): querytime = config.getint('querytime') or int(time.time()) - filename = join(config.get('outdir'), '%s.%s.%s.%s' % (data['lastSystem']['name'].strip(), data['lastStarport']['name'].strip(), time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(querytime)), csv and 'csv' or 'bpc')) + if not filename: + filename = join(config.get('outdir'), '%s.%s.%s.%s' % (data['lastSystem']['name'].strip(), data['lastStarport']['name'].strip(), time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(querytime)), csv and 'csv' or 'bpc')) timestamp = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(querytime)) header = 'System;Station;Commodity;Sell;Buy;Demand;;Supply;;Date;\n' diff --git a/companion.py b/companion.py index 1d469a6c..930e5a07 100644 --- a/companion.py +++ b/companion.py @@ -1,6 +1,3 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - import requests from collections import defaultdict from cookielib import LWPCookieJar @@ -14,6 +11,7 @@ import time if __debug__: from traceback import print_exc +from shipyard import ship_map from config import config holdoff = 60 # be nice @@ -47,34 +45,6 @@ commodity_map= { 'Terrain Enrichment Systems' : 'Land Enrichment Systems', } -ship_map = { - 'adder' : 'Adder', - 'anaconda' : 'Anaconda', - 'asp' : 'Asp', - 'cobramkiii' : 'Cobra Mk III', - 'diamondback' : 'Diamondback Scout', - 'diamondbackxl' : 'Diamondback Explorer', - 'eagle' : 'Eagle', - 'empire_courier' : 'Imperial Courier', - 'empire_eagle' : 'Imperial Eagle', - 'empire_fighter' : 'Imperial Fighter', - 'empire_trader' : 'Imperial Clipper', - 'federation_dropship' : 'Federal Dropship', - 'federation_dropship_mkii' : 'Federal Assault Ship', - 'federation_gunship' : 'Federal Gunship', - 'federation_fighter' : 'F63 Condor', - 'ferdelance' : 'Fer-de-Lance', - 'hauler' : 'Hauler', - 'orca' : 'Orca', - 'python' : 'Python', - 'sidewinder' : 'Sidewinder', - 'type6' : 'Type-6 Transporter', - 'type7' : 'Type-7 Transporter', - 'type9' : 'Type-9 Heavy', - 'viper' : 'Viper', - 'vulture' : 'Vulture', -} - # Companion API sometimes returns an array as a json array, sometimes as a json object indexed by "int". # This seems to depend on whether the there are 'gaps' in the Cmdr's data - i.e. whether the array is sparse. diff --git a/config.py b/config.py index d2d5e058..3a1f970b 100644 --- a/config.py +++ b/config.py @@ -7,6 +7,7 @@ from sys import platform appname = 'EDMarketConnector' applongname = 'E:D Market Connector' appversion = '1.8.3.0' +appcmdname = 'EDMC' if platform=='darwin': diff --git a/coriolis.py b/coriolis.py index 0eb4f69f..466cd419 100644 --- a/coriolis.py +++ b/coriolis.py @@ -9,7 +9,7 @@ import time from config import config import outfitting -import companion +import shipyard slot_map = { @@ -33,7 +33,7 @@ slot_map = { # https://raw.githubusercontent.com/jamesremuscat/EDDN/master/schemas/outfitting-v1.0-draft.json # http://cdn.coriolis.io/schemas/ship-loadout/2.json -ship_map = dict(companion.ship_map) +ship_map = dict(shipyard.ship_map) ship_map['asp'] = 'Asp Explorer' standard_map = OrderedDict([ # in output order @@ -74,11 +74,11 @@ fixup_map = { } -def export(data): +def export(data, filename=None): querytime = config.getint('querytime') or int(time.time()) - ship = companion.ship_map.get(data['ship']['name'].lower(), data['ship']['name']) + ship = shipyard.ship_map.get(data['ship']['name'].lower(), data['ship']['name']) loadout = OrderedDict([ # Mimic Coriolis export ordering ('$schema', 'http://cdn.coriolis.io/schemas/ship-loadout/2.json#'), @@ -170,6 +170,11 @@ def export(data): # Construct description string = json.dumps(loadout, indent=2) + if filename: + with open(filename, 'wt') as h: + h.write(string) + return + # Look for last ship of this type regexp = re.compile(re.escape(ship) + '\.\d\d\d\d\-\d\d\-\d\dT\d\d\.\d\d\.\d\d\.json') oldfiles = sorted([x for x in os.listdir(config.get('outdir')) if regexp.match(x)]) diff --git a/eddn.py b/eddn.py index d014b3c3..8fd5556e 100644 --- a/eddn.py +++ b/eddn.py @@ -9,7 +9,7 @@ from sys import platform import time from config import applongname, appversion, config -from companion import ship_map +from shipyard import ship_map import outfitting upload = 'http://eddn-gateway.elite-markets.net:8080/upload/' diff --git a/flightlog.py b/flightlog.py index d5ff7292..2d7b5d8b 100644 --- a/flightlog.py +++ b/flightlog.py @@ -8,7 +8,8 @@ from sys import platform import time from config import config -from companion import ship_map, commodity_map +from companion import commodity_map +from shipyard import ship_map logfile = None diff --git a/l10n.py b/l10n.py index 5ddc554f..b0fec932 100755 --- a/l10n.py +++ b/l10n.py @@ -19,6 +19,10 @@ class Translations: def __init__(self): self.translations = {} + def install_dummy(self): + # For when translation is not desired or not available + __builtin__.__dict__['_'] = lambda x: unicode(x).replace(u'{CR}', u'\n') # Promote strings to Unicode for consistency + def install(self): path = join(self.respath(), 'L10n') available = self.available() @@ -38,7 +42,7 @@ class Translations: lang = Translations.FALLBACK if lang not in self.available(): - __builtin__.__dict__['_'] = lambda x: unicode(x).replace(u'{CR}', u'\n') # Promote strings to Unicode for consistency + self.install_dummy() else: regexp = re.compile(r'\s*"([^"]+)"\s*=\s*"([^"]+)"\s*;\s*$') comment= re.compile(r'\s*/\*.*\*/\s*$') diff --git a/loadout.py b/loadout.py index 44141d3a..2651e6e8 100644 --- a/loadout.py +++ b/loadout.py @@ -8,7 +8,7 @@ import time from config import config import outfitting -from companion import ship_map +from shipyard import ship_map # API slot names to E:D Shipyard slot names @@ -28,7 +28,7 @@ slot_map = { 'fueltank' : 'FS', } -def export(data): +def export(data, filename=None): def class_rating(module): if 'guidance' in module: @@ -86,6 +86,11 @@ def export(data): string += '%s: %s\n' % (slot, name) string += '---\nCargo : %d T\nFuel : %d T\n' % (data['ship']['cargo']['capacity'], data['ship']['fuel']['capacity']) + if filename: + with open(filename, 'wt') as h: + h.write(string) + return + # Look for last ship of this type regexp = re.compile(re.escape(ship) + '\.\d\d\d\d\-\d\d\-\d\dT\d\d\.\d\d\.\d\d\.txt') oldfiles = sorted([x for x in os.listdir(config.get('outdir')) if regexp.match(x)]) diff --git a/outfitting.py b/outfitting.py index 9a8b7293..dc145026 100755 --- a/outfitting.py +++ b/outfitting.py @@ -8,8 +8,10 @@ import json import os from os.path import exists, isfile import sys +import time -from companion import ship_map +from shipyard import ship_map +from config import config outfile = 'outfitting.csv' @@ -322,6 +324,31 @@ def lookup(module): return new +def export(data, filename): + + querytime = config.getint('querytime') or int(time.time()) + + assert data['lastSystem'].get('name') + assert data['lastStarport'].get('name') + + timestamp = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(querytime)) + header = 'System,Station,Category,Name,Mount,Guidance,Ship,Class,Rating,Date\n' + rowheader = '%s,%s' % (data['lastSystem']['name'], data['lastStarport']['name']) + + h = open(filename, 'wt') + h.write(header) + for v in data['lastStarport'].get('modules', {}).itervalues(): + try: + m = lookup(v) + if m: + h.write('%s,%s,%s,%s,%s,%s,%s,%s,%s\n' % (rowheader, m['category'], m['name'], m.get('mount',''), m.get('guidance',''), m.get('ship',''), m['class'], m['rating'], timestamp)) + except AssertionError as e: + if __debug__: print 'Outfitting: %s' % e # Silently skip unrecognized modules + except: + if __debug__: raise + h.close() + + # add all the modules def addmodules(data): if not data.get('lastStarport'): diff --git a/setup.py b/setup.py index 9e4e3564..0025eef7 100755 --- a/setup.py +++ b/setup.py @@ -51,8 +51,10 @@ if sys.platform=='darwin': APP = 'EDMarketConnector.py' +APPCMD = 'EDMC.py' APPNAME = re.search(r"^appname\s*=\s*'(.+)'", file('config.py').read(), re.MULTILINE).group(1) APPLONGNAME = re.search(r"^applongname\s*=\s*'(.+)'", file('config.py').read(), re.MULTILINE).group(1) +APPCMDNAME = re.search(r"^appcmdname\s*=\s*'(.+)'", file('config.py').read(), re.MULTILINE).group(1) VERSION = re.search(r"^appversion\s*=\s*'(.+)'", file('config.py').read(), re.MULTILINE).group(1) SHORTVERSION = ''.join(VERSION.split('.')[:3]) @@ -119,6 +121,12 @@ setup( 'company_name': 'Marginal', # WinSparkle 'other_resources': [(24, 1, open(APPNAME+'.manifest').read())], } ], + console = [ {'dest_base': APPCMDNAME, + 'script': APPCMD, + 'copyright': u'© 2015 Jonathan Harris', + 'name': APPNAME, + 'company_name': 'Marginal', + } ], data_files = DATA_FILES, options = OPTIONS, setup_requires = [sys.platform=='darwin' and 'py2app' or 'py2exe'],