1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-18 18:07:37 +03:00

Add command-line program.

This commit is contained in:
Jonathan Harris 2015-11-07 05:05:25 +00:00
parent 9cad8aed9d
commit b0f60af38d
13 changed files with 211 additions and 43 deletions

114
EDMC.py Executable file
View File

@ -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)

View File

@ -84,6 +84,9 @@
<Component Guid="{9A0CB8A2-7167-492F-A185-0BDF7E9F5C01}"> <Component Guid="{9A0CB8A2-7167-492F-A185-0BDF7E9F5C01}">
<File KeyPath="yes" Source="SourceDir\EDMarketConnector.VisualElementsManifest.xml" /> <File KeyPath="yes" Source="SourceDir\EDMarketConnector.VisualElementsManifest.xml" />
</Component> </Component>
<Component Guid="*">
<File KeyPath="yes" Source="SourceDir\EDMC.exe" />
</Component>
<Component Guid="*"> <Component Guid="*">
<File KeyPath="yes" Source="SourceDir\fr.strings" /> <File KeyPath="yes" Source="SourceDir\fr.strings" />
</Component> </Component>
@ -359,6 +362,7 @@
<ComponentRef Id="cacert.pem" /> <ComponentRef Id="cacert.pem" />
<ComponentRef Id="EDMarketConnector.ico" /> <ComponentRef Id="EDMarketConnector.ico" />
<ComponentRef Id="EDMarketConnector.VisualElementsManifest.xml" /> <ComponentRef Id="EDMarketConnector.VisualElementsManifest.xml" />
<ComponentRef Id="EDMC.exe" />
<ComponentRef Id="fr.strings" /> <ComponentRef Id="fr.strings" />
<ComponentRef Id="it.strings" /> <ComponentRef Id="it.strings" />
<ComponentRef Id="library.zip" /> <ComponentRef Id="library.zip" />

View File

@ -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` . * 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` . * 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.
<ol start="0">
<li>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.</li>
<li>Server is down.</li>
<li>Invalid Credentials.</li>
<li>Verification Required.</li>
<li>Not docked. You have requested station data but the user is not docked at a station.</li>
<li>I/O or other OS error.</li>
</ol>
Packaging for distribution Packaging for distribution
-------- --------

3
bpc.py
View File

@ -14,10 +14,11 @@ bracketmap = { 0: '',
2: 'Med', 2: 'Med',
3: 'High', } 3: 'High', }
def export(data, csv=False): def export(data, csv=False, filename=None):
querytime = config.getint('querytime') or int(time.time()) querytime = config.getint('querytime') or int(time.time())
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')) 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)) timestamp = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(querytime))

View File

@ -1,6 +1,3 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import requests import requests
from collections import defaultdict from collections import defaultdict
from cookielib import LWPCookieJar from cookielib import LWPCookieJar
@ -14,6 +11,7 @@ import time
if __debug__: if __debug__:
from traceback import print_exc from traceback import print_exc
from shipyard import ship_map
from config import config from config import config
holdoff = 60 # be nice holdoff = 60 # be nice
@ -47,34 +45,6 @@ commodity_map= {
'Terrain Enrichment Systems' : 'Land Enrichment Systems', '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". # 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. # This seems to depend on whether the there are 'gaps' in the Cmdr's data - i.e. whether the array is sparse.

View File

@ -7,6 +7,7 @@ from sys import platform
appname = 'EDMarketConnector' appname = 'EDMarketConnector'
applongname = 'E:D Market Connector' applongname = 'E:D Market Connector'
appversion = '1.8.3.0' appversion = '1.8.3.0'
appcmdname = 'EDMC'
if platform=='darwin': if platform=='darwin':

View File

@ -9,7 +9,7 @@ import time
from config import config from config import config
import outfitting import outfitting
import companion import shipyard
slot_map = { slot_map = {
@ -33,7 +33,7 @@ slot_map = {
# https://raw.githubusercontent.com/jamesremuscat/EDDN/master/schemas/outfitting-v1.0-draft.json # https://raw.githubusercontent.com/jamesremuscat/EDDN/master/schemas/outfitting-v1.0-draft.json
# http://cdn.coriolis.io/schemas/ship-loadout/2.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' ship_map['asp'] = 'Asp Explorer'
standard_map = OrderedDict([ # in output order 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()) 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 loadout = OrderedDict([ # Mimic Coriolis export ordering
('$schema', 'http://cdn.coriolis.io/schemas/ship-loadout/2.json#'), ('$schema', 'http://cdn.coriolis.io/schemas/ship-loadout/2.json#'),
@ -170,6 +170,11 @@ def export(data):
# Construct description # Construct description
string = json.dumps(loadout, indent=2) 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 # 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') 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)]) oldfiles = sorted([x for x in os.listdir(config.get('outdir')) if regexp.match(x)])

View File

@ -9,7 +9,7 @@ from sys import platform
import time import time
from config import applongname, appversion, config from config import applongname, appversion, config
from companion import ship_map from shipyard import ship_map
import outfitting import outfitting
upload = 'http://eddn-gateway.elite-markets.net:8080/upload/' upload = 'http://eddn-gateway.elite-markets.net:8080/upload/'

View File

@ -8,7 +8,8 @@ from sys import platform
import time import time
from config import config from config import config
from companion import ship_map, commodity_map from companion import commodity_map
from shipyard import ship_map
logfile = None logfile = None

View File

@ -19,6 +19,10 @@ class Translations:
def __init__(self): def __init__(self):
self.translations = {} 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): def install(self):
path = join(self.respath(), 'L10n') path = join(self.respath(), 'L10n')
available = self.available() available = self.available()
@ -38,7 +42,7 @@ class Translations:
lang = Translations.FALLBACK lang = Translations.FALLBACK
if lang not in self.available(): 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: else:
regexp = re.compile(r'\s*"([^"]+)"\s*=\s*"([^"]+)"\s*;\s*$') regexp = re.compile(r'\s*"([^"]+)"\s*=\s*"([^"]+)"\s*;\s*$')
comment= re.compile(r'\s*/\*.*\*/\s*$') comment= re.compile(r'\s*/\*.*\*/\s*$')

View File

@ -8,7 +8,7 @@ import time
from config import config from config import config
import outfitting import outfitting
from companion import ship_map from shipyard import ship_map
# API slot names to E:D Shipyard slot names # API slot names to E:D Shipyard slot names
@ -28,7 +28,7 @@ slot_map = {
'fueltank' : 'FS', 'fueltank' : 'FS',
} }
def export(data): def export(data, filename=None):
def class_rating(module): def class_rating(module):
if 'guidance' in module: if 'guidance' in module:
@ -86,6 +86,11 @@ def export(data):
string += '%s: %s\n' % (slot, name) string += '%s: %s\n' % (slot, name)
string += '---\nCargo : %d T\nFuel : %d T\n' % (data['ship']['cargo']['capacity'], data['ship']['fuel']['capacity']) 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 # 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') 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)]) oldfiles = sorted([x for x in os.listdir(config.get('outdir')) if regexp.match(x)])

View File

@ -8,8 +8,10 @@ import json
import os import os
from os.path import exists, isfile from os.path import exists, isfile
import sys import sys
import time
from companion import ship_map from shipyard import ship_map
from config import config
outfile = 'outfitting.csv' outfile = 'outfitting.csv'
@ -322,6 +324,31 @@ def lookup(module):
return new 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 # add all the modules
def addmodules(data): def addmodules(data):
if not data.get('lastStarport'): if not data.get('lastStarport'):

View File

@ -51,8 +51,10 @@ if sys.platform=='darwin':
APP = 'EDMarketConnector.py' APP = 'EDMarketConnector.py'
APPCMD = 'EDMC.py'
APPNAME = re.search(r"^appname\s*=\s*'(.+)'", file('config.py').read(), re.MULTILINE).group(1) 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) 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) VERSION = re.search(r"^appversion\s*=\s*'(.+)'", file('config.py').read(), re.MULTILINE).group(1)
SHORTVERSION = ''.join(VERSION.split('.')[:3]) SHORTVERSION = ''.join(VERSION.split('.')[:3])
@ -119,6 +121,12 @@ setup(
'company_name': 'Marginal', # WinSparkle 'company_name': 'Marginal', # WinSparkle
'other_resources': [(24, 1, open(APPNAME+'.manifest').read())], '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, data_files = DATA_FILES,
options = OPTIONS, options = OPTIONS,
setup_requires = [sys.platform=='darwin' and 'py2app' or 'py2exe'], setup_requires = [sys.platform=='darwin' and 'py2app' or 'py2exe'],