tradedangerous 12.7.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py.typed +1 -0
- trade.py +49 -0
- tradedangerous/__init__.py +43 -0
- tradedangerous/cache.py +1381 -0
- tradedangerous/cli.py +136 -0
- tradedangerous/commands/TEMPLATE.py +74 -0
- tradedangerous/commands/__init__.py +244 -0
- tradedangerous/commands/buildcache_cmd.py +102 -0
- tradedangerous/commands/buy_cmd.py +427 -0
- tradedangerous/commands/commandenv.py +372 -0
- tradedangerous/commands/exceptions.py +94 -0
- tradedangerous/commands/export_cmd.py +150 -0
- tradedangerous/commands/import_cmd.py +222 -0
- tradedangerous/commands/local_cmd.py +243 -0
- tradedangerous/commands/market_cmd.py +207 -0
- tradedangerous/commands/nav_cmd.py +252 -0
- tradedangerous/commands/olddata_cmd.py +270 -0
- tradedangerous/commands/parsing.py +221 -0
- tradedangerous/commands/rares_cmd.py +298 -0
- tradedangerous/commands/run_cmd.py +1521 -0
- tradedangerous/commands/sell_cmd.py +262 -0
- tradedangerous/commands/shipvendor_cmd.py +60 -0
- tradedangerous/commands/station_cmd.py +68 -0
- tradedangerous/commands/trade_cmd.py +181 -0
- tradedangerous/commands/update_cmd.py +67 -0
- tradedangerous/corrections.py +55 -0
- tradedangerous/csvexport.py +234 -0
- tradedangerous/db/__init__.py +27 -0
- tradedangerous/db/adapter.py +192 -0
- tradedangerous/db/config.py +107 -0
- tradedangerous/db/engine.py +259 -0
- tradedangerous/db/lifecycle.py +332 -0
- tradedangerous/db/locks.py +208 -0
- tradedangerous/db/orm_models.py +500 -0
- tradedangerous/db/paths.py +113 -0
- tradedangerous/db/utils.py +661 -0
- tradedangerous/edscupdate.py +565 -0
- tradedangerous/edsmupdate.py +474 -0
- tradedangerous/formatting.py +210 -0
- tradedangerous/fs.py +156 -0
- tradedangerous/gui.py +1146 -0
- tradedangerous/mapping.py +133 -0
- tradedangerous/mfd/__init__.py +103 -0
- tradedangerous/mfd/saitek/__init__.py +3 -0
- tradedangerous/mfd/saitek/directoutput.py +678 -0
- tradedangerous/mfd/saitek/x52pro.py +195 -0
- tradedangerous/misc/checkpricebounds.py +287 -0
- tradedangerous/misc/clipboard.py +49 -0
- tradedangerous/misc/coord64.py +83 -0
- tradedangerous/misc/csvdialect.py +57 -0
- tradedangerous/misc/derp-sentinel.py +35 -0
- tradedangerous/misc/diff-system-csvs.py +159 -0
- tradedangerous/misc/eddb.py +81 -0
- tradedangerous/misc/eddn.py +349 -0
- tradedangerous/misc/edsc.py +437 -0
- tradedangerous/misc/edsm.py +121 -0
- tradedangerous/misc/importeddbstats.py +54 -0
- tradedangerous/misc/prices-json-exp.py +179 -0
- tradedangerous/misc/progress.py +194 -0
- tradedangerous/plugins/__init__.py +249 -0
- tradedangerous/plugins/edcd_plug.py +371 -0
- tradedangerous/plugins/eddblink_plug.py +861 -0
- tradedangerous/plugins/edmc_batch_plug.py +133 -0
- tradedangerous/plugins/spansh_plug.py +2647 -0
- tradedangerous/prices.py +211 -0
- tradedangerous/submit-distances.py +422 -0
- tradedangerous/templates/Added.csv +37 -0
- tradedangerous/templates/Category.csv +17 -0
- tradedangerous/templates/RareItem.csv +143 -0
- tradedangerous/templates/TradeDangerous.sql +338 -0
- tradedangerous/tools.py +40 -0
- tradedangerous/tradecalc.py +1302 -0
- tradedangerous/tradedb.py +2320 -0
- tradedangerous/tradeenv.py +313 -0
- tradedangerous/tradeenv.pyi +109 -0
- tradedangerous/tradeexcept.py +131 -0
- tradedangerous/tradeorm.py +183 -0
- tradedangerous/transfers.py +192 -0
- tradedangerous/utils.py +243 -0
- tradedangerous/version.py +16 -0
- tradedangerous-12.7.6.dist-info/METADATA +106 -0
- tradedangerous-12.7.6.dist-info/RECORD +87 -0
- tradedangerous-12.7.6.dist-info/WHEEL +5 -0
- tradedangerous-12.7.6.dist-info/entry_points.txt +3 -0
- tradedangerous-12.7.6.dist-info/licenses/LICENSE +373 -0
- tradedangerous-12.7.6.dist-info/top_level.txt +2 -0
- tradegui.py +24 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
from .exceptions import (
|
|
2
|
+
FleetCarrierError, OdysseyError, PadSizeError, PlanetaryError,
|
|
3
|
+
)
|
|
4
|
+
|
|
5
|
+
######################################################################
|
|
6
|
+
# Parsing Helpers
|
|
7
|
+
|
|
8
|
+
class ParseArgument:
|
|
9
|
+
"""
|
|
10
|
+
Provides argument forwarding so that 'makeSubParser' can take function-like arguments.
|
|
11
|
+
"""
|
|
12
|
+
def __init__(self, *args, **kwargs):
|
|
13
|
+
self.args, self.kwargs = args, kwargs
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MutuallyExclusiveGroup:
|
|
17
|
+
def __init__(self, *args):
|
|
18
|
+
self.arguments = list(args)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
######################################################################
|
|
22
|
+
# Derived Parsers
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CreditParser(int):
|
|
26
|
+
"""
|
|
27
|
+
argparse helper for parsing numeric prefixes, i.e.
|
|
28
|
+
'k' for thousands, 'm' for millions and 'b' for billions.
|
|
29
|
+
"""
|
|
30
|
+
suffixes = {'k': 10**3, 'm': 10**6, 'b': 10**9}
|
|
31
|
+
|
|
32
|
+
def __new__(cls, val, **kwargs):
|
|
33
|
+
if isinstance(val, str):
|
|
34
|
+
if val[-1].lower() in CreditParser.suffixes:
|
|
35
|
+
val = int(float(val[:-1]) * CreditParser.suffixes[val[-1].lower()])
|
|
36
|
+
return super().__new__(cls, val, **kwargs)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class PadSizeArgument(int):
|
|
40
|
+
"""
|
|
41
|
+
argparse helper for --pad-size
|
|
42
|
+
"""
|
|
43
|
+
class PadSizeParser(str):
|
|
44
|
+
def __new__(cls, val, **kwargs):
|
|
45
|
+
if not isinstance(val, str):
|
|
46
|
+
raise PadSizeError(val)
|
|
47
|
+
for v in val:
|
|
48
|
+
if "SML?".find(v.upper()) < 0:
|
|
49
|
+
raise PadSizeError(val.upper())
|
|
50
|
+
return super().__new__(cls, val, **kwargs)
|
|
51
|
+
|
|
52
|
+
def __init__(self):
|
|
53
|
+
self.args = ('--pad-size', '-p',)
|
|
54
|
+
self.kwargs = {
|
|
55
|
+
'help': (
|
|
56
|
+
'Limit to stations with one of the specified pad sizes, '
|
|
57
|
+
'e.g. --pad SML? matches any pad, --pad M matches only '
|
|
58
|
+
'medium pads.'
|
|
59
|
+
),
|
|
60
|
+
'dest': 'padSize',
|
|
61
|
+
'metavar': 'PADSIZES',
|
|
62
|
+
'type': 'padsize',
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class AvoidPlacesArgument(ParseArgument):
|
|
67
|
+
def __init__(self):
|
|
68
|
+
self.args = ['--avoid']
|
|
69
|
+
self.kwargs = {
|
|
70
|
+
'action': 'append',
|
|
71
|
+
'help': (
|
|
72
|
+
"Don't list results for the specified systems or stations.\n"
|
|
73
|
+
"Names can be one-per '--avoid' or comma separated, e.g. "
|
|
74
|
+
"'--avoid a,b,c' or '--avoid a,b --avoid c'"
|
|
75
|
+
),
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class SwitchArgument(ParseArgument):
|
|
80
|
+
def __init__(self, help=None):
|
|
81
|
+
if isinstance(self.switches, (tuple, list)):
|
|
82
|
+
self.args = self.switches
|
|
83
|
+
else:
|
|
84
|
+
self.args = (self.switches,)
|
|
85
|
+
help = help or self.help
|
|
86
|
+
self.kwargs = {'action': 'store_true', 'dest': self.dest, 'help': help}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class BlackMarketSwitch(SwitchArgument):
|
|
90
|
+
switches = ['--black-market', '--bm']
|
|
91
|
+
dest = 'blackMarket'
|
|
92
|
+
help = 'Require stations known to have a black market.'
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class ShipyardSwitch(SwitchArgument):
|
|
96
|
+
switches = ['--shipyard']
|
|
97
|
+
dest = 'shipyard'
|
|
98
|
+
help = 'Require stations known to have a Shipyard.'
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class OutfittingSwitch(SwitchArgument):
|
|
102
|
+
switches = ['--outfitting']
|
|
103
|
+
dest = 'outfitting'
|
|
104
|
+
help = 'Require stations known to have Outfitting.'
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class RearmSwitch(SwitchArgument):
|
|
108
|
+
switches = ['--rearm']
|
|
109
|
+
dest = 'rearm'
|
|
110
|
+
help = 'Require stations known to sell munitions.'
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class RefuelSwitch(SwitchArgument):
|
|
114
|
+
switches = ['--refuel']
|
|
115
|
+
dest = 'refuel'
|
|
116
|
+
help = 'Require stations known to sell fuel.'
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class RepairSwitch(SwitchArgument):
|
|
120
|
+
switches = ['--repair']
|
|
121
|
+
dest = 'repair'
|
|
122
|
+
help = 'Require stations known to offer repairs.'
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class NoPlanetSwitch(SwitchArgument):
|
|
126
|
+
switches = ['--no-planet']
|
|
127
|
+
dest = 'noPlanet'
|
|
128
|
+
help = 'Require stations to be in space.'
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class PlanetaryArgument(int):
|
|
132
|
+
"""
|
|
133
|
+
argparse helper for --planetary
|
|
134
|
+
"""
|
|
135
|
+
class PlanetaryParser(str):
|
|
136
|
+
def __new__(cls, val, **kwargs):
|
|
137
|
+
if not isinstance(val, str):
|
|
138
|
+
raise PlanetaryError(val)
|
|
139
|
+
for v in val:
|
|
140
|
+
if "YN?".find(v.upper()) < 0:
|
|
141
|
+
raise PlanetaryError(val.upper())
|
|
142
|
+
return super().__new__(cls, val, **kwargs)
|
|
143
|
+
|
|
144
|
+
def __init__(self):
|
|
145
|
+
self.args = ['--planetary']
|
|
146
|
+
self.kwargs = {
|
|
147
|
+
'help': (
|
|
148
|
+
'Limit to stations with one of the specified planetary, '
|
|
149
|
+
'e.g. --pla YN? matches any station, --pla Y matches only '
|
|
150
|
+
'planetary stations.'
|
|
151
|
+
),
|
|
152
|
+
'dest': 'planetary',
|
|
153
|
+
'metavar': 'PLANETARY',
|
|
154
|
+
'type': 'planetary',
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class FleetCarrierArgument(int):
|
|
159
|
+
"""
|
|
160
|
+
argparse helper for --fleet-carrier
|
|
161
|
+
"""
|
|
162
|
+
class FleetCarrierParser(str):
|
|
163
|
+
def __new__(cls, val, **kwargs):
|
|
164
|
+
if not isinstance(val, str):
|
|
165
|
+
raise FleetCarrierError(val)
|
|
166
|
+
for v in val:
|
|
167
|
+
if "YN?".find(v.upper()) < 0:
|
|
168
|
+
raise FleetCarrierError(val.upper())
|
|
169
|
+
return super().__new__(cls, val, **kwargs)
|
|
170
|
+
|
|
171
|
+
def __init__(self):
|
|
172
|
+
self.args = ['--fleet-carrier', '--fc']
|
|
173
|
+
self.kwargs = {
|
|
174
|
+
'help': (
|
|
175
|
+
'Limit to stations with one of the specified fleet-carrier, '
|
|
176
|
+
'e.g. --fc YN? matches any station, --fc Y matches only '
|
|
177
|
+
'fleet-carrier stations.'
|
|
178
|
+
),
|
|
179
|
+
'dest': 'fleet',
|
|
180
|
+
'metavar': 'FLEET',
|
|
181
|
+
'type': 'fleet',
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
class OdysseyArgument(int):
|
|
185
|
+
"""
|
|
186
|
+
argparse helper for --odyssey
|
|
187
|
+
"""
|
|
188
|
+
class OdysseyParser(str):
|
|
189
|
+
def __new__(cls, val, **kwargs):
|
|
190
|
+
if not isinstance(val, str):
|
|
191
|
+
raise OdysseyError(val)
|
|
192
|
+
for v in val:
|
|
193
|
+
if "YN?".find(v.upper()) < 0:
|
|
194
|
+
raise OdysseyError(val.upper())
|
|
195
|
+
return super().__new__(cls, val, **kwargs)
|
|
196
|
+
|
|
197
|
+
def __init__(self):
|
|
198
|
+
self.args = ['--odyssey', '--od']
|
|
199
|
+
self.kwargs = {
|
|
200
|
+
'help': (
|
|
201
|
+
'Limit to stations with one of the specified odyssey, '
|
|
202
|
+
'e.g. --od YN? matches any station, --od Y matches only '
|
|
203
|
+
'odyssey stations.'
|
|
204
|
+
),
|
|
205
|
+
'dest': 'odyssey',
|
|
206
|
+
'metavar': 'ODYSSEY',
|
|
207
|
+
'type': 'odyssey',
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
__tdParserHelpers = {
|
|
212
|
+
'credits': CreditParser,
|
|
213
|
+
'padsize': PadSizeArgument.PadSizeParser,
|
|
214
|
+
'planetary': PlanetaryArgument.PlanetaryParser,
|
|
215
|
+
'fleet': FleetCarrierArgument.FleetCarrierParser,
|
|
216
|
+
'odyssey': OdysseyArgument.OdysseyParser,
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
def registerParserHelpers(into):
|
|
220
|
+
for typeName, helper in __tdParserHelpers.items():
|
|
221
|
+
into.register('type', typeName, helper)
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
from .commandenv import ResultRow
|
|
2
|
+
from .exceptions import CommandLineError
|
|
3
|
+
from .parsing import (
|
|
4
|
+
PadSizeArgument, ParseArgument, MutuallyExclusiveGroup, NoPlanetSwitch,
|
|
5
|
+
PlanetaryArgument, FleetCarrierArgument, OdysseyArgument,
|
|
6
|
+
)
|
|
7
|
+
from ..formatting import RowFormat, max_len
|
|
8
|
+
from ..tradedb import TradeDB, select, SA_Category, SA_RareItem, SA_Station
|
|
9
|
+
|
|
10
|
+
import time
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
######################################################################
|
|
14
|
+
# Parser config
|
|
15
|
+
|
|
16
|
+
# Displayed on the "trade.py --help" command list, etc.
|
|
17
|
+
help='Find rares near your current local.'
|
|
18
|
+
# Should match the name of the module minus the _cmd.py
|
|
19
|
+
name='rares'
|
|
20
|
+
# Displayed at the end of the "trade.py rares --help"
|
|
21
|
+
epilog=None
|
|
22
|
+
# Set to False in commands that need to operate without
|
|
23
|
+
# a trade database.
|
|
24
|
+
wantsTradeDB=True
|
|
25
|
+
# Required parameters
|
|
26
|
+
arguments = [
|
|
27
|
+
ParseArgument(
|
|
28
|
+
'near',
|
|
29
|
+
help='Your current system.',
|
|
30
|
+
type=str,
|
|
31
|
+
metavar='SYSTEMNAME',
|
|
32
|
+
),
|
|
33
|
+
]
|
|
34
|
+
# Optional parameters
|
|
35
|
+
switches = [
|
|
36
|
+
ParseArgument('--ly',
|
|
37
|
+
help='Maximum distance to search.',
|
|
38
|
+
metavar='LY',
|
|
39
|
+
type=float,
|
|
40
|
+
default=180,
|
|
41
|
+
dest='maxLyPer',
|
|
42
|
+
),
|
|
43
|
+
ParseArgument('--limit',
|
|
44
|
+
help='Maximum number of results to list.',
|
|
45
|
+
default=None,
|
|
46
|
+
type=int,
|
|
47
|
+
),
|
|
48
|
+
PadSizeArgument(),
|
|
49
|
+
MutuallyExclusiveGroup(
|
|
50
|
+
NoPlanetSwitch(),
|
|
51
|
+
PlanetaryArgument(),
|
|
52
|
+
),
|
|
53
|
+
FleetCarrierArgument(),
|
|
54
|
+
OdysseyArgument(),
|
|
55
|
+
ParseArgument('--price-sort', '-P',
|
|
56
|
+
help='Sort by price not distance.',
|
|
57
|
+
action='store_true',
|
|
58
|
+
default=False,
|
|
59
|
+
dest='sortByPrice',
|
|
60
|
+
),
|
|
61
|
+
ParseArgument('--reverse', '-r',
|
|
62
|
+
help='Reverse the list.',
|
|
63
|
+
action='store_true',
|
|
64
|
+
default=False,
|
|
65
|
+
),
|
|
66
|
+
ParseArgument('--away',
|
|
67
|
+
help='Require "--from" systems to be at least this far from primary system',
|
|
68
|
+
metavar='LY',
|
|
69
|
+
default=0,
|
|
70
|
+
type=float,
|
|
71
|
+
),
|
|
72
|
+
MutuallyExclusiveGroup(
|
|
73
|
+
ParseArgument('--legal',
|
|
74
|
+
help='List only items known to be legal.',
|
|
75
|
+
action='store_true',
|
|
76
|
+
),
|
|
77
|
+
ParseArgument('--illegal',
|
|
78
|
+
help='List only items known to be illegal.',
|
|
79
|
+
action='store_true',
|
|
80
|
+
)
|
|
81
|
+
),
|
|
82
|
+
ParseArgument('--from',
|
|
83
|
+
help='Additional systems to range check candidates against, requires --away.',
|
|
84
|
+
metavar='SYSTEMNAME',
|
|
85
|
+
action='append',
|
|
86
|
+
dest='awayFrom',
|
|
87
|
+
),
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
######################################################################
|
|
91
|
+
# Perform query and populate result set
|
|
92
|
+
|
|
93
|
+
def run(results, cmdenv, tdb):
|
|
94
|
+
"""
|
|
95
|
+
Fetch all the data needed to display the results of a "rares"
|
|
96
|
+
command. Does not actually print anything.
|
|
97
|
+
"""
|
|
98
|
+
# Lookup the system we're currently in.
|
|
99
|
+
start = cmdenv.nearSystem
|
|
100
|
+
# Hoist the padSize, noPlanet and planetary parameter for convenience
|
|
101
|
+
padSize = cmdenv.padSize
|
|
102
|
+
noPlanet = cmdenv.noPlanet
|
|
103
|
+
planetary = cmdenv.planetary
|
|
104
|
+
fleet = cmdenv.fleet
|
|
105
|
+
odyssey = cmdenv.odyssey
|
|
106
|
+
# How far we're want to cast our net.
|
|
107
|
+
maxLy = float(cmdenv.maxLyPer or 0.0)
|
|
108
|
+
|
|
109
|
+
awaySystems = set()
|
|
110
|
+
if cmdenv.away or cmdenv.awayFrom:
|
|
111
|
+
if not cmdenv.away or not cmdenv.awayFrom:
|
|
112
|
+
raise CommandLineError("Invalid --away/--from usage. See --help")
|
|
113
|
+
minAwayDist = cmdenv.away
|
|
114
|
+
for sysName in cmdenv.awayFrom:
|
|
115
|
+
system = tdb.lookupPlace(sysName).system
|
|
116
|
+
awaySystems.add(system)
|
|
117
|
+
|
|
118
|
+
# Start to build up the results data.
|
|
119
|
+
results.summary = ResultRow()
|
|
120
|
+
results.summary.near = start
|
|
121
|
+
results.summary.ly = maxLy
|
|
122
|
+
results.summary.awaySystems = awaySystems
|
|
123
|
+
|
|
124
|
+
distCheckFn = start.distanceTo
|
|
125
|
+
|
|
126
|
+
# Look through the rares list.
|
|
127
|
+
stmt = select(
|
|
128
|
+
SA_RareItem.rare_id,
|
|
129
|
+
SA_RareItem.station_id,
|
|
130
|
+
SA_RareItem.name,
|
|
131
|
+
SA_RareItem.cost,
|
|
132
|
+
SA_RareItem.max_allocation,
|
|
133
|
+
SA_RareItem.illegal,
|
|
134
|
+
SA_RareItem.suppressed,
|
|
135
|
+
SA_Category.name
|
|
136
|
+
).join(SA_Category)
|
|
137
|
+
if cmdenv.illegal or cmdenv.legal:
|
|
138
|
+
stmt = stmt.where(SA_RareItem.illegal == ('Y' if cmdenv.legal else 'N'))
|
|
139
|
+
if noPlanet:
|
|
140
|
+
stmt = stmt.join(SA_Station).where(SA_Station.planetary != 'Y')
|
|
141
|
+
|
|
142
|
+
awaySystems = set()
|
|
143
|
+
|
|
144
|
+
started = time.time()
|
|
145
|
+
with tdb.Session() as session:
|
|
146
|
+
rows = session.execute(stmt).all()
|
|
147
|
+
|
|
148
|
+
for rare in rows:
|
|
149
|
+
stn = tdb.stationByID[rare.station_id]
|
|
150
|
+
if padSize and not stn.checkPadSize(padSize):
|
|
151
|
+
continue
|
|
152
|
+
if planetary and not stn.checkPlanetary(planetary):
|
|
153
|
+
continue
|
|
154
|
+
if fleet and not stn.checkFleet(fleet):
|
|
155
|
+
continue
|
|
156
|
+
if odyssey and not stn.checkOdyssey(odyssey):
|
|
157
|
+
continue
|
|
158
|
+
|
|
159
|
+
rareSys = stn.system
|
|
160
|
+
dist = distCheckFn(rareSys)
|
|
161
|
+
if maxLy > 0.0 and dist > maxLy:
|
|
162
|
+
continue
|
|
163
|
+
|
|
164
|
+
if awaySystems:
|
|
165
|
+
awayCheck = rareSys.distanceTo
|
|
166
|
+
if any(awayCheck(away) < minAwayDist for away in awaySystems):
|
|
167
|
+
continue
|
|
168
|
+
|
|
169
|
+
row = ResultRow()
|
|
170
|
+
row.rare = rare
|
|
171
|
+
row.station = stn # <-- IMPORTANT: used by render()
|
|
172
|
+
row.dist = dist
|
|
173
|
+
results.rows.append(row)
|
|
174
|
+
|
|
175
|
+
cmdenv.DEBUG0("Found {:n} rares in {:.3f}s", len(results.rows), time.time() - started)
|
|
176
|
+
|
|
177
|
+
# Was anything matched?
|
|
178
|
+
if not results.rows:
|
|
179
|
+
print("No matches found.")
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
# Sort safely even if rare.costCr is None (treat None as 0)
|
|
183
|
+
def price_key(row):
|
|
184
|
+
return row.rare.cost or 0
|
|
185
|
+
|
|
186
|
+
if cmdenv.sortByPrice:
|
|
187
|
+
results.rows.sort(key=lambda row: row.dist)
|
|
188
|
+
results.rows.sort(key=price_key, reverse=True)
|
|
189
|
+
else:
|
|
190
|
+
results.rows.sort(key=price_key, reverse=True)
|
|
191
|
+
results.rows.sort(key=lambda row: row.dist)
|
|
192
|
+
|
|
193
|
+
if cmdenv.reverse:
|
|
194
|
+
results.rows.reverse()
|
|
195
|
+
|
|
196
|
+
limit = cmdenv.limit or 0
|
|
197
|
+
if limit > 0:
|
|
198
|
+
results.rows = results.rows[:limit]
|
|
199
|
+
|
|
200
|
+
return results
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
#######################################################################
|
|
206
|
+
## Transform result set into output
|
|
207
|
+
|
|
208
|
+
def render(results, cmdenv, tdb):
|
|
209
|
+
"""
|
|
210
|
+
Render output for 'rares' with robust None-handling.
|
|
211
|
+
Keeps existing column order/labels.
|
|
212
|
+
"""
|
|
213
|
+
rows = results.rows
|
|
214
|
+
if not rows:
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
# Helpers to coalesce possibly-missing attributes
|
|
218
|
+
def _cost(row):
|
|
219
|
+
try:
|
|
220
|
+
v = row.rare.cost
|
|
221
|
+
return int(v) if v is not None else 0
|
|
222
|
+
except Exception:
|
|
223
|
+
return 0
|
|
224
|
+
|
|
225
|
+
def _rare_name(row):
|
|
226
|
+
try:
|
|
227
|
+
n = row.rare.name
|
|
228
|
+
return n or "?"
|
|
229
|
+
except Exception:
|
|
230
|
+
return "?"
|
|
231
|
+
|
|
232
|
+
def _alloc(row):
|
|
233
|
+
val = row.rare.max_allocation
|
|
234
|
+
return str(val) if val not in (None, "") else "?"
|
|
235
|
+
|
|
236
|
+
def _rare_illegal(row):
|
|
237
|
+
val = row.rare.illegal
|
|
238
|
+
return val if val in ("Y", "N", "?") else "?"
|
|
239
|
+
|
|
240
|
+
def _stn_ls(row):
|
|
241
|
+
try:
|
|
242
|
+
v = row.station.distFromStar()
|
|
243
|
+
return v if v is not None else "?"
|
|
244
|
+
except Exception:
|
|
245
|
+
return "?"
|
|
246
|
+
|
|
247
|
+
def _dist(row):
|
|
248
|
+
try:
|
|
249
|
+
return float(getattr(row, "dist", 0.0) or 0.0)
|
|
250
|
+
except Exception:
|
|
251
|
+
return 0.0
|
|
252
|
+
|
|
253
|
+
def _stn_bm(row):
|
|
254
|
+
key = getattr(row.station, "blackMarket", "?")
|
|
255
|
+
return TradeDB.marketStates.get(key, key or "?")
|
|
256
|
+
|
|
257
|
+
def _pad(row):
|
|
258
|
+
key = getattr(row.station, "maxPadSize", "?")
|
|
259
|
+
return TradeDB.padSizes.get(key, key or "?")
|
|
260
|
+
|
|
261
|
+
def _plt(row):
|
|
262
|
+
key = getattr(row.station, "planetary", "?")
|
|
263
|
+
return TradeDB.planetStates.get(key, key or "?")
|
|
264
|
+
|
|
265
|
+
def _flc(row):
|
|
266
|
+
key = getattr(row.station, "fleet", "?")
|
|
267
|
+
return TradeDB.fleetStates.get(key, key or "?")
|
|
268
|
+
|
|
269
|
+
def _ody(row):
|
|
270
|
+
key = getattr(row.station, "odyssey", "?")
|
|
271
|
+
return TradeDB.odysseyStates.get(key, key or "?")
|
|
272
|
+
|
|
273
|
+
# Column widths based on safe key functions
|
|
274
|
+
max_stn = max_len(rows, key=lambda r: r.station.name())
|
|
275
|
+
max_rare = max_len(rows, key=lambda r: _rare_name(r))
|
|
276
|
+
|
|
277
|
+
rowFmt = RowFormat()
|
|
278
|
+
rowFmt.addColumn('Station', '<', max_stn, key=lambda r: r.station.name())
|
|
279
|
+
rowFmt.addColumn('Rare', '<', max_rare, key=lambda r: _rare_name(r))
|
|
280
|
+
rowFmt.addColumn('Cost', '>', 10, 'n', key=lambda r: _cost(r))
|
|
281
|
+
rowFmt.addColumn('DistLy', '>', 6, '.2f', key=lambda r: _dist(r))
|
|
282
|
+
rowFmt.addColumn('Alloc', '>', 5, key=lambda r: _alloc(r))
|
|
283
|
+
# First B/mkt: rare legality flag (Y/N/?)
|
|
284
|
+
rowFmt.addColumn('B/mkt', '>', 4, key=lambda r: _rare_illegal(r))
|
|
285
|
+
rowFmt.addColumn('StnLs', '>', 10, key=lambda r: _stn_ls(r))
|
|
286
|
+
# Second B/mkt: station black market availability via mapping
|
|
287
|
+
rowFmt.addColumn('B/mkt', '>', 4, key=lambda r: _stn_bm(r))
|
|
288
|
+
rowFmt.addColumn('Pad', '>', 3, key=lambda r: _pad(r))
|
|
289
|
+
rowFmt.addColumn('Plt', '>', 3, key=lambda r: _plt(r))
|
|
290
|
+
rowFmt.addColumn('Flc', '>', 3, key=lambda r: _flc(r))
|
|
291
|
+
rowFmt.addColumn('Ody', '>', 3, key=lambda r: _ody(r))
|
|
292
|
+
|
|
293
|
+
if not cmdenv.quiet:
|
|
294
|
+
heading, underline = rowFmt.heading()
|
|
295
|
+
print(heading, underline, sep='\n')
|
|
296
|
+
|
|
297
|
+
for row in rows:
|
|
298
|
+
print(rowFmt.format(row))
|