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.
+
+ - 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.
+ - Server is down.
+ - Invalid Credentials.
+ - Verification Required.
+ - Not docked. You have requested station data but the user is not docked at a station.
+ - I/O or other OS error.
+
+
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'],