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.
Files changed (87) hide show
  1. py.typed +1 -0
  2. trade.py +49 -0
  3. tradedangerous/__init__.py +43 -0
  4. tradedangerous/cache.py +1381 -0
  5. tradedangerous/cli.py +136 -0
  6. tradedangerous/commands/TEMPLATE.py +74 -0
  7. tradedangerous/commands/__init__.py +244 -0
  8. tradedangerous/commands/buildcache_cmd.py +102 -0
  9. tradedangerous/commands/buy_cmd.py +427 -0
  10. tradedangerous/commands/commandenv.py +372 -0
  11. tradedangerous/commands/exceptions.py +94 -0
  12. tradedangerous/commands/export_cmd.py +150 -0
  13. tradedangerous/commands/import_cmd.py +222 -0
  14. tradedangerous/commands/local_cmd.py +243 -0
  15. tradedangerous/commands/market_cmd.py +207 -0
  16. tradedangerous/commands/nav_cmd.py +252 -0
  17. tradedangerous/commands/olddata_cmd.py +270 -0
  18. tradedangerous/commands/parsing.py +221 -0
  19. tradedangerous/commands/rares_cmd.py +298 -0
  20. tradedangerous/commands/run_cmd.py +1521 -0
  21. tradedangerous/commands/sell_cmd.py +262 -0
  22. tradedangerous/commands/shipvendor_cmd.py +60 -0
  23. tradedangerous/commands/station_cmd.py +68 -0
  24. tradedangerous/commands/trade_cmd.py +181 -0
  25. tradedangerous/commands/update_cmd.py +67 -0
  26. tradedangerous/corrections.py +55 -0
  27. tradedangerous/csvexport.py +234 -0
  28. tradedangerous/db/__init__.py +27 -0
  29. tradedangerous/db/adapter.py +192 -0
  30. tradedangerous/db/config.py +107 -0
  31. tradedangerous/db/engine.py +259 -0
  32. tradedangerous/db/lifecycle.py +332 -0
  33. tradedangerous/db/locks.py +208 -0
  34. tradedangerous/db/orm_models.py +500 -0
  35. tradedangerous/db/paths.py +113 -0
  36. tradedangerous/db/utils.py +661 -0
  37. tradedangerous/edscupdate.py +565 -0
  38. tradedangerous/edsmupdate.py +474 -0
  39. tradedangerous/formatting.py +210 -0
  40. tradedangerous/fs.py +156 -0
  41. tradedangerous/gui.py +1146 -0
  42. tradedangerous/mapping.py +133 -0
  43. tradedangerous/mfd/__init__.py +103 -0
  44. tradedangerous/mfd/saitek/__init__.py +3 -0
  45. tradedangerous/mfd/saitek/directoutput.py +678 -0
  46. tradedangerous/mfd/saitek/x52pro.py +195 -0
  47. tradedangerous/misc/checkpricebounds.py +287 -0
  48. tradedangerous/misc/clipboard.py +49 -0
  49. tradedangerous/misc/coord64.py +83 -0
  50. tradedangerous/misc/csvdialect.py +57 -0
  51. tradedangerous/misc/derp-sentinel.py +35 -0
  52. tradedangerous/misc/diff-system-csvs.py +159 -0
  53. tradedangerous/misc/eddb.py +81 -0
  54. tradedangerous/misc/eddn.py +349 -0
  55. tradedangerous/misc/edsc.py +437 -0
  56. tradedangerous/misc/edsm.py +121 -0
  57. tradedangerous/misc/importeddbstats.py +54 -0
  58. tradedangerous/misc/prices-json-exp.py +179 -0
  59. tradedangerous/misc/progress.py +194 -0
  60. tradedangerous/plugins/__init__.py +249 -0
  61. tradedangerous/plugins/edcd_plug.py +371 -0
  62. tradedangerous/plugins/eddblink_plug.py +861 -0
  63. tradedangerous/plugins/edmc_batch_plug.py +133 -0
  64. tradedangerous/plugins/spansh_plug.py +2647 -0
  65. tradedangerous/prices.py +211 -0
  66. tradedangerous/submit-distances.py +422 -0
  67. tradedangerous/templates/Added.csv +37 -0
  68. tradedangerous/templates/Category.csv +17 -0
  69. tradedangerous/templates/RareItem.csv +143 -0
  70. tradedangerous/templates/TradeDangerous.sql +338 -0
  71. tradedangerous/tools.py +40 -0
  72. tradedangerous/tradecalc.py +1302 -0
  73. tradedangerous/tradedb.py +2320 -0
  74. tradedangerous/tradeenv.py +313 -0
  75. tradedangerous/tradeenv.pyi +109 -0
  76. tradedangerous/tradeexcept.py +131 -0
  77. tradedangerous/tradeorm.py +183 -0
  78. tradedangerous/transfers.py +192 -0
  79. tradedangerous/utils.py +243 -0
  80. tradedangerous/version.py +16 -0
  81. tradedangerous-12.7.6.dist-info/METADATA +106 -0
  82. tradedangerous-12.7.6.dist-info/RECORD +87 -0
  83. tradedangerous-12.7.6.dist-info/WHEEL +5 -0
  84. tradedangerous-12.7.6.dist-info/entry_points.txt +3 -0
  85. tradedangerous-12.7.6.dist-info/licenses/LICENSE +373 -0
  86. tradedangerous-12.7.6.dist-info/top_level.txt +2 -0
  87. tradegui.py +24 -0
@@ -0,0 +1,252 @@
1
+ from .commandenv import ResultRow
2
+ from .parsing import (
3
+ AvoidPlacesArgument, FleetCarrierArgument, MutuallyExclusiveGroup,
4
+ NoPlanetSwitch, OdysseyArgument, PadSizeArgument, ParseArgument,
5
+ PlanetaryArgument,
6
+ )
7
+
8
+ from tradedangerous import TradeDB, TradeException
9
+ from tradedangerous.tradedb import Station, System
10
+ from tradedangerous.formatting import RowFormat, ColumnFormat
11
+
12
+
13
+ ######################################################################
14
+ # Parser config
15
+
16
+ help='Calculate a route between two systems.'
17
+ name='nav'
18
+ epilog=None
19
+ wantsTradeDB=True
20
+ arguments = [
21
+ ParseArgument('starting', help='System to start from', type=str),
22
+ ParseArgument('ending', help='System to end at', type=str),
23
+ ]
24
+ switches = [
25
+ ParseArgument('--ly-per',
26
+ help='Maximum light years per jump.',
27
+ dest='maxLyPer',
28
+ metavar='N.NN',
29
+ type=float,
30
+ ),
31
+ AvoidPlacesArgument(),
32
+ ParseArgument('--via',
33
+ help='Require specified systems/stations to be en-route (in order).',
34
+ action='append',
35
+ metavar='PLACE[,PLACE,...]',
36
+ ),
37
+ ParseArgument('--stations', '-S',
38
+ help='Include station details.',
39
+ action='store_true',
40
+ ),
41
+ ParseArgument('--refuel-jumps',
42
+ help='Require a station after this many jumps',
43
+ type=int,
44
+ dest='stationInterval',
45
+ ),
46
+ PadSizeArgument(),
47
+ MutuallyExclusiveGroup(
48
+ NoPlanetSwitch(),
49
+ PlanetaryArgument(),
50
+ ),
51
+ FleetCarrierArgument(),
52
+ OdysseyArgument(),
53
+ ]
54
+
55
+ ######################################################################
56
+ # Helpers
57
+
58
+
59
+ class NoRouteError(TradeException):
60
+ """ Exception denoting specifically a route could not be found. """
61
+
62
+
63
+ ######################################################################
64
+ # Perform query and populate result set
65
+
66
+ def run(results, cmdenv, tdb):
67
+ srcSystem, dstSystem = cmdenv.origPlace, cmdenv.destPlace
68
+ if isinstance(srcSystem, Station):
69
+ srcSystem = srcSystem.system
70
+ if isinstance(dstSystem, Station):
71
+ dstSystem = dstSystem.system
72
+
73
+ maxLyPer = cmdenv.maxLyPer or cmdenv.maxSystemLinkLy
74
+
75
+ cmdenv.DEBUG0("Route from {} to {} with max {}ly per jump.",
76
+ srcSystem.name(), dstSystem.name(), maxLyPer)
77
+
78
+ # Build a list of src->dst pairs
79
+ hops = [ [ srcSystem, None ] ]
80
+ if cmdenv.viaPlaces:
81
+ for hop in cmdenv.viaPlaces:
82
+ hops[-1][1] = hop
83
+ hops.append([hop, None])
84
+ hops[-1][1] = dstSystem
85
+
86
+ avoiding = [
87
+ avoid for avoid in cmdenv.avoidPlaces
88
+ if isinstance(avoid, System)
89
+ ]
90
+
91
+ route = [ ]
92
+ stationInterval = cmdenv.stationInterval
93
+ for hop in hops:
94
+ try:
95
+ hopRoute = list(tdb.getRoute(
96
+ hop[0], hop[1],
97
+ maxLyPer,
98
+ avoiding,
99
+ stationInterval=stationInterval,
100
+ ))
101
+ except TypeError:
102
+ raise NoRouteError(
103
+ "No route found between {} and {} "
104
+ "with a max {}ly/jump limit.".format(
105
+ hop[0].name(), hop[1].name(),
106
+ maxLyPer,
107
+ ))
108
+ route = route[:-1] + hopRoute
109
+
110
+ results.summary = ResultRow(
111
+ fromSys=srcSystem,
112
+ toSys=dstSystem,
113
+ maxLy=maxLyPer,
114
+ )
115
+
116
+ lastSys, totalLy, dirLy = srcSystem, 0.00, 0.00
117
+ maxPadSize = cmdenv.padSize
118
+ planetary = cmdenv.planetary
119
+ fleet = cmdenv.fleet
120
+ odyssey = cmdenv.odyssey
121
+ noPlanet = cmdenv.noPlanet
122
+
123
+ for (jumpSys, dist) in route:
124
+ jumpLy = lastSys.distanceTo(jumpSys)
125
+ totalLy += jumpLy
126
+ if cmdenv.detail:
127
+ dirLy = jumpSys.distanceTo(dstSystem)
128
+ row = ResultRow(
129
+ action='Via',
130
+ system=jumpSys,
131
+ jumpLy=jumpLy,
132
+ totalLy=totalLy,
133
+ dirLy=dirLy,
134
+ )
135
+ row.stations = []
136
+ if cmdenv.stations:
137
+ for (station) in jumpSys.stations:
138
+ if maxPadSize and not station.checkPadSize(maxPadSize):
139
+ continue
140
+ if planetary and not station.checkPlanetary(planetary):
141
+ continue
142
+ if fleet and not station.checkFleet(fleet):
143
+ continue
144
+ if odyssey and not station.checkOdyssey(odyssey):
145
+ continue
146
+ if noPlanet and station.planetary != 'N':
147
+ continue
148
+ rr = ResultRow(
149
+ station=station,
150
+ age=station.itemDataAgeStr,
151
+ )
152
+ row.stations.append(rr)
153
+ results.rows.append(row)
154
+ lastSys = jumpSys
155
+ results.rows[0].action='Depart'
156
+ results.rows[-1].action='Arrive'
157
+
158
+ return results
159
+
160
+ ######################################################################
161
+ # Transform result set into output
162
+
163
+ def render(results, cmdenv, tdb):
164
+ if cmdenv.quiet > 1:
165
+ print(','.join(row.system.name() for row in results.rows))
166
+ return False
167
+
168
+ longestNamed = max(results.rows,
169
+ key=lambda row: len(row.system.name()))
170
+ longestNameLen = len(longestNamed.system.name())
171
+
172
+ rowFmt = RowFormat()
173
+ if cmdenv.detail:
174
+ rowFmt.addColumn("Action", '<', 6, post=":", key=lambda row: row.action)
175
+ rowFmt.addColumn("System", '<', longestNameLen,
176
+ key=lambda row: row.system.name())
177
+ rowFmt.addColumn("JumpLy", '>', '7', '.2f',
178
+ key=lambda row: row.jumpLy)
179
+ if cmdenv.detail:
180
+ rowFmt.addColumn("Stations", '>', 2,
181
+ key=lambda row: len(row.system.stations))
182
+ if cmdenv.detail:
183
+ rowFmt.addColumn("DistLy", '>', '7', '.2f',
184
+ key=lambda row: row.totalLy)
185
+ if cmdenv.detail > 1:
186
+ rowFmt.addColumn("DirLy", '>', 7, '.2f',
187
+ key=lambda row: row.dirLy)
188
+
189
+ showStations = cmdenv.stations
190
+ if showStations:
191
+ stnRowFmt = RowFormat(prefix=' / ').append(
192
+ ColumnFormat("Station", '<', 38,
193
+ key=lambda row: row.station.dbname)
194
+ ).append(
195
+ ColumnFormat("StnLs", '>', '10',
196
+ key=lambda row: row.station.distFromStar())
197
+ ).append(
198
+ ColumnFormat("Age/days", '>', 7,
199
+ key=lambda row: row.age)
200
+ ).append(
201
+ ColumnFormat('Mkt', '>', '3',
202
+ key=lambda row: TradeDB.marketStates[row.station.market])
203
+ ).append(
204
+ ColumnFormat("BMk", '>', '3',
205
+ key=lambda row: TradeDB.marketStates[row.station.blackMarket])
206
+ ).append(
207
+ ColumnFormat("Shp", '>', '3',
208
+ key=lambda row: TradeDB.marketStates[row.station.shipyard])
209
+ ).append(
210
+ ColumnFormat("Out", '>', '3',
211
+ key=lambda row: TradeDB.marketStates[row.station.outfitting])
212
+ ).append(
213
+ ColumnFormat("Arm", '>', '3',
214
+ key=lambda row: TradeDB.marketStates[row.station.rearm])
215
+ ).append(
216
+ ColumnFormat("Ref", '>', '3',
217
+ key=lambda row: TradeDB.marketStates[row.station.refuel])
218
+ ).append(
219
+ ColumnFormat("Rep", '>', '3',
220
+ key=lambda row: TradeDB.marketStates[row.station.repair])
221
+ ).append(
222
+ ColumnFormat("Pad", '>', '3',
223
+ key=lambda row: TradeDB.padSizes[row.station.maxPadSize])
224
+ ).append(
225
+ ColumnFormat("Plt", '>', '3',
226
+ key=lambda row: TradeDB.planetStates[row.station.planetary])
227
+ ).append(
228
+ ColumnFormat("Flc", '>', '3',
229
+ key=lambda row: TradeDB.fleetStates[row.station.fleet])
230
+ ).append(
231
+ ColumnFormat("Ody", '>', '3',
232
+ key=lambda row: TradeDB.odysseyStates[row.station.odyssey])
233
+ )
234
+ if cmdenv.detail > 1:
235
+ stnRowFmt.append(
236
+ ColumnFormat("Itms", ">", 4,
237
+ key=lambda row: row.station.itemCount)
238
+ )
239
+
240
+ if not cmdenv.quiet:
241
+ heading, underline = rowFmt.heading()
242
+ if showStations:
243
+ print(heading)
244
+ heading, underline = stnRowFmt.heading()
245
+ print(heading, underline, sep='\n')
246
+
247
+ for row in results.rows:
248
+ print(rowFmt.format(row))
249
+ for stnRow in row.stations:
250
+ print(stnRowFmt.format(stnRow))
251
+
252
+ return results
@@ -0,0 +1,270 @@
1
+ from .parsing import (
2
+ FleetCarrierArgument, MutuallyExclusiveGroup, NoPlanetSwitch,
3
+ OdysseyArgument, ParseArgument, PadSizeArgument, PlanetaryArgument,
4
+ )
5
+ from ..tradedb import TradeDB
6
+ from ..tradeexcept import TradeException
7
+ from sqlalchemy import select, table, column, func, literal
8
+ from sqlalchemy.orm import Session
9
+
10
+
11
+ ######################################################################
12
+ # Parser config
13
+
14
+ name='olddata'
15
+ help='Show oldest data in database.'
16
+ epilog=None
17
+ wantsTradeDB=True
18
+ arguments = [
19
+ ]
20
+ switches = [
21
+ ParseArgument('--limit',
22
+ help='Maximum number of results to show',
23
+ default=20,
24
+ type=int,
25
+ ),
26
+ ParseArgument('--near',
27
+ help='Find sellers within jump range of this system.',
28
+ type=str
29
+ ),
30
+ ParseArgument('--ly',
31
+ help='[Requires --near] Systems within this range of --near.',
32
+ default=None,
33
+ dest='maxLyPer',
34
+ metavar='N.NN',
35
+ type=float,
36
+ ),
37
+ ParseArgument('--route',
38
+ help='Sort to shortest path',
39
+ action='store_true',
40
+ ),
41
+ ParseArgument('--min-age',
42
+ help='List data older than this number of days.',
43
+ type=float,
44
+ dest='minAge',
45
+ ),
46
+ PadSizeArgument(),
47
+ MutuallyExclusiveGroup(
48
+ NoPlanetSwitch(),
49
+ PlanetaryArgument(),
50
+ ),
51
+ FleetCarrierArgument(),
52
+ OdysseyArgument(),
53
+ ParseArgument('--ls-max',
54
+ help='Only consider stations upto this many ls from their star.',
55
+ metavar='LS',
56
+ dest='maxLs',
57
+ type=int,
58
+ default=0,
59
+ ),
60
+ ]
61
+
62
+ ######################################################################
63
+ # Perform query and populate result set
64
+
65
+ def run(results, cmdenv, tdb):
66
+ """
67
+ Backend-neutral rework:
68
+ - Use SQLAlchemy Core to compute age and (optionally) distance².
69
+ - age: age_in_days(session, MAX(StationItem.modified)) AS age_days
70
+ - dist2: (sys.pos_x - x0)^2 + (sys.pos_y - y0)^2 + (sys.pos_z - z0)^2 (when --near)
71
+ - Materialize rows to avoid closed-cursor errors.
72
+ - Preserve downstream filters/sorting/limit exactly as before.
73
+ """
74
+ from .commandenv import ResultRow
75
+ from tradedangerous.db.utils import age_in_days
76
+
77
+ cmdenv = results.cmdenv
78
+ tdb = cmdenv.tdb
79
+
80
+ results.summary = ResultRow()
81
+ results.limit = cmdenv.limit
82
+
83
+ # SQLAlchemy lightweight table defs
84
+ si = table(
85
+ "StationItem",
86
+ column("station_id"),
87
+ column("item_id"),
88
+ column("modified"),
89
+ )
90
+ stn = table(
91
+ "Station",
92
+ column("station_id"),
93
+ column("system_id"),
94
+ column("ls_from_star"),
95
+ )
96
+ sys_tbl = table(
97
+ "System",
98
+ column("system_id"),
99
+ column("pos_x"),
100
+ column("pos_y"),
101
+ column("pos_z"),
102
+ )
103
+
104
+ # Build session bound to current engine (age_in_days needs the session)
105
+ session = Session(bind=tdb.engine)
106
+
107
+ # Base SELECT: station_id, ls_from_star, age_days
108
+ age_expr = age_in_days(session, func.max(si.c.modified)).label("age_days")
109
+
110
+ # Optional near-system distance²
111
+ nearSys = cmdenv.nearSystem
112
+ if nearSys:
113
+ dx = (sys_tbl.c.pos_x - literal(nearSys.posX))
114
+ dy = (sys_tbl.c.pos_y - literal(nearSys.posY))
115
+ dz = (sys_tbl.c.pos_z - literal(nearSys.posZ))
116
+ dist2_expr = (dx * dx + dy * dy + dz * dz).label("d2")
117
+ else:
118
+ dist2_expr = literal(0.0).label("d2")
119
+
120
+ stmt = (
121
+ select(
122
+ si.c.station_id,
123
+ age_expr,
124
+ stn.c.ls_from_star,
125
+ dist2_expr,
126
+ )
127
+ .select_from(
128
+ si.join(stn, stn.c.station_id == si.c.station_id)
129
+ .join(sys_tbl, sys_tbl.c.system_id == stn.c.system_id) if nearSys
130
+ else si.join(stn, stn.c.station_id == si.c.station_id)
131
+ )
132
+ .group_by(si.c.station_id, stn.c.ls_from_star, dist2_expr)
133
+ .order_by(age_expr.desc())
134
+ )
135
+
136
+ # Bounding box for near (keeps scan small, mirrors original)
137
+ if nearSys:
138
+ maxLy = cmdenv.maxLyPer or cmdenv.maxSystemLinkLy
139
+ # Bounding box predicate
140
+ stmt = stmt.where(
141
+ sys_tbl.c.pos_x.between(nearSys.posX - maxLy, nearSys.posX + maxLy),
142
+ sys_tbl.c.pos_y.between(nearSys.posY - maxLy, nearSys.posY + maxLy),
143
+ sys_tbl.c.pos_z.between(nearSys.posZ - maxLy, nearSys.posZ + maxLy),
144
+ )
145
+ # Radius filter: HAVING dist2 <= maxLy^2
146
+ stmt = stmt.having(dist2_expr <= (maxLy * maxLy))
147
+
148
+ # Min-age filter (apply to aggregated age of MAX(modified))
149
+ if cmdenv.minAge:
150
+ stmt = stmt.having(age_expr >= float(cmdenv.minAge))
151
+
152
+ # Execute and materialize rows
153
+ rows = session.execute(stmt).fetchall()
154
+ session.close()
155
+
156
+ # Downstream filters (unchanged)
157
+ padSize = cmdenv.padSize
158
+ planetary = cmdenv.planetary
159
+ fleet = cmdenv.fleet
160
+ odyssey = cmdenv.odyssey
161
+ noPlanet = cmdenv.noPlanet
162
+ mls = cmdenv.maxLs
163
+
164
+ for (stnID, age, ls, dist2) in rows:
165
+ cmdenv.DEBUG2("{}:{}:{}", stnID, age, ls)
166
+ row = ResultRow()
167
+ row.station = tdb.stationByID[stnID]
168
+ row.age = float(age or 0.0)
169
+ row.ls = "{:n}".format(ls) if ls else "?"
170
+ row.dist = (float(dist2) ** 0.5) if dist2 else 0.0
171
+
172
+ if padSize and not row.station.checkPadSize(padSize):
173
+ continue
174
+ if planetary and not row.station.checkPlanetary(planetary):
175
+ continue
176
+ if fleet and not row.station.checkFleet(fleet):
177
+ continue
178
+ if odyssey and not row.station.checkOdyssey(odyssey):
179
+ continue
180
+ if noPlanet and row.station.planetary != 'N':
181
+ continue
182
+ if mls and row.station.lsFromStar > mls:
183
+ continue
184
+
185
+ results.rows.append(row)
186
+
187
+ # Route optimization and limiting (unchanged)
188
+ if cmdenv.route and len(results.rows) > 1:
189
+ def walk(start_idx, dist):
190
+ rows_ = results.rows
191
+ startNode = rows_[start_idx]
192
+ openList = set(rows_)
193
+ path = [startNode]
194
+ openList.remove(startNode)
195
+ while len(path) < len(rows_):
196
+ lastNode = path[-1]
197
+ distFn = lastNode.station.system.distanceTo
198
+ nearest = min(openList, key=lambda r: distFn(r.station.system))
199
+ openList.remove(nearest)
200
+ path.append(nearest)
201
+ dist += distFn(nearest.station.system)
202
+ return (path, dist)
203
+
204
+ if cmdenv.near:
205
+ bestPath = walk(0, results.rows[0].dist)
206
+ else:
207
+ bestPath = (results.rows, float("inf"))
208
+ for i in range(len(results.rows)):
209
+ candidate = walk(i, 0)
210
+ if candidate[1] < bestPath[1]:
211
+ bestPath = candidate
212
+ results.rows[:] = bestPath[0]
213
+
214
+ if cmdenv.limit:
215
+ results.rows[:] = results.rows[:cmdenv.limit]
216
+
217
+ return results
218
+
219
+
220
+ ######################################################################
221
+ # Transform result set into output
222
+
223
+ def render(results, cmdenv, tdb):
224
+ from ..formatting import RowFormat, ColumnFormat
225
+
226
+ if not results or not results.rows:
227
+ raise TradeException("No data found")
228
+
229
+ # Compare system names so we can tell
230
+ longestNamed = max(results.rows,
231
+ key=lambda row: len(row.station.name()))
232
+ longestNameLen = len(longestNamed.station.name())
233
+
234
+ rowFmt = RowFormat().append(
235
+ ColumnFormat("Station", '<', longestNameLen,
236
+ key=lambda row: row.station.name())
237
+ )
238
+
239
+ if cmdenv.quiet < 2:
240
+ if cmdenv.nearSystem:
241
+ rowFmt.addColumn('DistLy', '>', 6, '.2f',
242
+ key=lambda row: row.dist
243
+ )
244
+
245
+ rowFmt.append(
246
+ ColumnFormat("Age/days", '>', '8', '.2f',
247
+ key=lambda row: row.age)
248
+ ).append(
249
+ ColumnFormat("StnLs", '>', '10',
250
+ key=lambda row: row.station.distFromStar())
251
+ ).append(
252
+ ColumnFormat("Pad", '>', '3',
253
+ key=lambda row: TradeDB.padSizes[row.station.maxPadSize])
254
+ ).append(
255
+ ColumnFormat("Plt", '>', '3',
256
+ key=lambda row: TradeDB.planetStates[row.station.planetary])
257
+ ).append(
258
+ ColumnFormat("Flc", '>', '3',
259
+ key=lambda row: TradeDB.fleetStates[row.station.fleet])
260
+ ).append(
261
+ ColumnFormat("Ody", '>', '3',
262
+ key=lambda row: TradeDB.odysseyStates[row.station.odyssey])
263
+ )
264
+
265
+ if not cmdenv.quiet:
266
+ heading, underline = rowFmt.heading()
267
+ print(heading, underline, sep='\n')
268
+
269
+ for row in results.rows:
270
+ print(rowFmt.format(row))