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,195 @@
|
|
|
1
|
+
"""
|
|
2
|
+
x52pro.py - DirectOutputDevice for SaitekX52Pro
|
|
3
|
+
|
|
4
|
+
Version: 0.2
|
|
5
|
+
Author: Frazzle
|
|
6
|
+
Version: 0.3
|
|
7
|
+
Author: Oliver "kfsone" Smith <oliver@kfs.org> 2014
|
|
8
|
+
|
|
9
|
+
Description: Python class implementing specific functions for Saitek X52 Pro
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
TODO:
|
|
13
|
+
* Error handling and exceptions
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from tradedangerous.mfd.saitek.directoutput import DirectOutputDevice
|
|
17
|
+
|
|
18
|
+
import sys
|
|
19
|
+
import time
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class X52Pro(DirectOutputDevice):
|
|
23
|
+
class Page:
|
|
24
|
+
_lines = [ str(), str(), str() ]
|
|
25
|
+
_leds = dict()
|
|
26
|
+
|
|
27
|
+
def __init__(self, device, page_id, name, active):
|
|
28
|
+
self.device = device
|
|
29
|
+
self.page_id = page_id
|
|
30
|
+
self.name = name
|
|
31
|
+
self.device.AddPage(self.page_id, name, 1 if active else 0)
|
|
32
|
+
self.active = active
|
|
33
|
+
|
|
34
|
+
def __del__(self, *args, **kwargs):
|
|
35
|
+
try:
|
|
36
|
+
self.device.RemovePage(self.page_id)
|
|
37
|
+
except AttributeError:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
def __getitem__(self, key):
|
|
41
|
+
return self._lines[key]
|
|
42
|
+
|
|
43
|
+
def __setitem__(self, key, value):
|
|
44
|
+
self._lines[key] = value
|
|
45
|
+
if self.active:
|
|
46
|
+
self.device.SetString(self.page_id, key, value)
|
|
47
|
+
|
|
48
|
+
def activate(self):
|
|
49
|
+
if self.active is True:
|
|
50
|
+
return
|
|
51
|
+
self.device.AddPage(self.page_id, self.name, 1)
|
|
52
|
+
|
|
53
|
+
def refresh(self):
|
|
54
|
+
# Resend strings to the display
|
|
55
|
+
for lineNo, string in enumerate(self._lines):
|
|
56
|
+
self.device.SetString(self.page_id, lineNo, string)
|
|
57
|
+
for led, value in self._leds:
|
|
58
|
+
self.device.SetLed(self.page_id, led, 1 if value else 0)
|
|
59
|
+
|
|
60
|
+
def set_led(self, led, value):
|
|
61
|
+
self._leds[led] = value
|
|
62
|
+
if self.active:
|
|
63
|
+
self.device.SetLed(self.page_id, led, 1 if value else 0)
|
|
64
|
+
|
|
65
|
+
def set_led_colour(self, value, led_red, led_green):
|
|
66
|
+
if value == "red":
|
|
67
|
+
self.set_led(led_red, 1)
|
|
68
|
+
self.set_led(led_green, 0)
|
|
69
|
+
elif value == "green":
|
|
70
|
+
self.set_led(led_red, 0)
|
|
71
|
+
self.set_led(led_green, 1)
|
|
72
|
+
elif value == "orange":
|
|
73
|
+
self.set_led(led_red, 1)
|
|
74
|
+
self.set_led(led_green, 1)
|
|
75
|
+
elif value == "off":
|
|
76
|
+
self.set_led(led_red, 0)
|
|
77
|
+
self.set_led(led_green, 0)
|
|
78
|
+
|
|
79
|
+
def fire(self, value):
|
|
80
|
+
self.set_led(0, value)
|
|
81
|
+
|
|
82
|
+
def fire_a(self, value):
|
|
83
|
+
self.set_led_colour(value, 1, 2)
|
|
84
|
+
|
|
85
|
+
def fire_b(self, value):
|
|
86
|
+
self.set_led_colour(value, 3, 4)
|
|
87
|
+
|
|
88
|
+
def fire_d(self, value):
|
|
89
|
+
self.set_led_colour(value, 5, 6)
|
|
90
|
+
|
|
91
|
+
def fire_e(self, value):
|
|
92
|
+
self.set_led_colour(value, 7, 8)
|
|
93
|
+
|
|
94
|
+
def toggle_1_2(self, value):
|
|
95
|
+
self.set_led_colour(value, 9, 10)
|
|
96
|
+
|
|
97
|
+
def toggle_3_4(self, value):
|
|
98
|
+
self.set_led_colour(value, 11, 12)
|
|
99
|
+
|
|
100
|
+
def toggle_5_6(self, value):
|
|
101
|
+
self.set_led_colour(value, 13, 14)
|
|
102
|
+
|
|
103
|
+
def pov_2(self, value):
|
|
104
|
+
self.set_led_colour(value, 15, 16)
|
|
105
|
+
|
|
106
|
+
def clutch(self, value):
|
|
107
|
+
self.set_led_colour(value, 17, 18)
|
|
108
|
+
|
|
109
|
+
def throttle_axis(self, value):
|
|
110
|
+
self.set_led(19, value)
|
|
111
|
+
|
|
112
|
+
pages = {}
|
|
113
|
+
_page_counter = 0
|
|
114
|
+
|
|
115
|
+
def add_page(self, name, active=True):
|
|
116
|
+
page = self.pages[name] = self.Page(self, self._page_counter, name, active=active)
|
|
117
|
+
self._page_counter += 1
|
|
118
|
+
return page
|
|
119
|
+
|
|
120
|
+
def remove_page(self, name):
|
|
121
|
+
del self.pages[name]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def OnPage(self, page_id, activated):
|
|
125
|
+
super().OnPage(page_id, activated)
|
|
126
|
+
for page in self.pages.values():
|
|
127
|
+
if page.page_id == page_id:
|
|
128
|
+
print("Found the page", page_id, activated)
|
|
129
|
+
if activated:
|
|
130
|
+
page.refresh()
|
|
131
|
+
else:
|
|
132
|
+
page.active = False
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def OnSoftButton(self, *args, **kwargs):
|
|
137
|
+
super().OnSoftButton(*args, **kwargs)
|
|
138
|
+
print("*** ON SOFT BUTTON")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def finish(self):
|
|
142
|
+
for page in self.pages:
|
|
143
|
+
del page
|
|
144
|
+
super().finish()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
if __name__ == '__main__':
|
|
148
|
+
x52 = X52Pro(debug_level=1)
|
|
149
|
+
print("X52 Connected")
|
|
150
|
+
|
|
151
|
+
x52.add_page("Page1")
|
|
152
|
+
print("Page1 page added")
|
|
153
|
+
|
|
154
|
+
x52.pages["Page1"][0] = "Test String"
|
|
155
|
+
try:
|
|
156
|
+
x52.pages["Page1"][5] = "FAIL"
|
|
157
|
+
raise ValueError("Your stick thinks it has 5 lines of MFD, I think it lies.")
|
|
158
|
+
except IndexError:
|
|
159
|
+
print("Your MFD has the correct number of lines of text")
|
|
160
|
+
|
|
161
|
+
x52.pages["Page1"][1] = "-- Page 1 [0]"
|
|
162
|
+
|
|
163
|
+
fireToggle = False
|
|
164
|
+
x52.pages["Page1"].fire(fireToggle)
|
|
165
|
+
x52.pages["Page1"].fire_a("amber")
|
|
166
|
+
x52.pages["Page1"].toggle_3_4("red")
|
|
167
|
+
x52.pages["Page1"].throttle_axis(~fireToggle)
|
|
168
|
+
|
|
169
|
+
print("Adding second page")
|
|
170
|
+
x52.add_page("Page2", active=False)
|
|
171
|
+
x52.pages["Page2"][0] = "Second Page Is Right Here"
|
|
172
|
+
x52.pages["Page2"][1] = "-- Page 2 [1]"
|
|
173
|
+
|
|
174
|
+
print("Looping")
|
|
175
|
+
|
|
176
|
+
loopNo = 0
|
|
177
|
+
while True:
|
|
178
|
+
try:
|
|
179
|
+
loopNo += 1
|
|
180
|
+
x52.pages["Page1"][2] = "Loop #" + str(loopNo)
|
|
181
|
+
time.sleep(0.25)
|
|
182
|
+
x52.pages["Page1"].fire_a("red")
|
|
183
|
+
time.sleep(0.25)
|
|
184
|
+
x52.pages["Page1"].fire_a("orange")
|
|
185
|
+
time.sleep(0.25)
|
|
186
|
+
x52.pages["Page1"].fire_a("green")
|
|
187
|
+
time.sleep(0.25)
|
|
188
|
+
x52.pages["Page1"].fire_a("off")
|
|
189
|
+
fireToggle = ~fireToggle
|
|
190
|
+
x52.pages["Page1"].fire(fireToggle)
|
|
191
|
+
x52.pages["Page1"].throttle_axis(~fireToggle)
|
|
192
|
+
except Exception as e:
|
|
193
|
+
print(e)
|
|
194
|
+
x52.finish()
|
|
195
|
+
sys.exit()
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
#! /usr/bin/env python
|
|
2
|
+
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
import argparse
|
|
5
|
+
import colorama
|
|
6
|
+
import tradedb
|
|
7
|
+
import tradeenv
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
colorama.init()
|
|
11
|
+
|
|
12
|
+
def check_price_bounds(
|
|
13
|
+
tdb,
|
|
14
|
+
table,
|
|
15
|
+
margin=25,
|
|
16
|
+
deletePrices="tmp/deletions.prices",
|
|
17
|
+
deleteSql="tmp/deletions.sql",
|
|
18
|
+
doDeletions=False,
|
|
19
|
+
percentile=5,
|
|
20
|
+
errorFilter=None,
|
|
21
|
+
):
|
|
22
|
+
assert isinstance(percentile, (int,float))
|
|
23
|
+
assert percentile >= 0.01 and percentile < 50
|
|
24
|
+
|
|
25
|
+
lowP = percentile / 100
|
|
26
|
+
highP = 1 - lowP
|
|
27
|
+
|
|
28
|
+
mask = "{:>7}:{:<28}|{:>8}|{:>8}|{:>8}|{:>8}|{:>8}|{:>8}|{:>8}|{:>8}|{:>8} {}"
|
|
29
|
+
header = "{}:\n".format(table)
|
|
30
|
+
header += mask.format(
|
|
31
|
+
"#prices", "name",
|
|
32
|
+
"p0", "low", "p{}".format(percentile),
|
|
33
|
+
"p50", "avg", "midavg",
|
|
34
|
+
"p{}".format(100-percentile), "high", "p100",
|
|
35
|
+
""
|
|
36
|
+
)
|
|
37
|
+
header += "\n" + '-' * len(header)
|
|
38
|
+
print(header)
|
|
39
|
+
|
|
40
|
+
deletions = []
|
|
41
|
+
stations = defaultdict(list)
|
|
42
|
+
|
|
43
|
+
def remediation(item, compare, value):
|
|
44
|
+
deletion = (
|
|
45
|
+
"DELETE FROM {} "
|
|
46
|
+
"WHERE item_id = {} "
|
|
47
|
+
"AND price > 0 AND price {} {}".format(
|
|
48
|
+
table, item.ID, compare, value
|
|
49
|
+
))
|
|
50
|
+
deletions.append((deletion, "{}".format(item.dbname)))
|
|
51
|
+
count = 0
|
|
52
|
+
for (stnID,) in tdb.query("""
|
|
53
|
+
SELECT station_id FROM {}
|
|
54
|
+
WHERE item_id = {} AND price > 0 AND price {} {}
|
|
55
|
+
""".format(
|
|
56
|
+
table, item.ID, compare, value,
|
|
57
|
+
)):
|
|
58
|
+
stations[stnID].append((item.ID, compare, value))
|
|
59
|
+
count += 1
|
|
60
|
+
return count
|
|
61
|
+
|
|
62
|
+
# Distance between p50 and p5 or p95 is 45.
|
|
63
|
+
# We use multiplier to expand projections so that our thresholds
|
|
64
|
+
# behave as though they are pX to p50+M instead of pX to p50.
|
|
65
|
+
multiplier = (50 + margin) / (50 - lowP)
|
|
66
|
+
|
|
67
|
+
for item in tdb.itemByID.values():
|
|
68
|
+
if item.dbname.upper() in tdb.tdenv.ignore:
|
|
69
|
+
continue
|
|
70
|
+
cur = tdb.query("""
|
|
71
|
+
SELECT price
|
|
72
|
+
FROM {}
|
|
73
|
+
WHERE item_id = ?
|
|
74
|
+
AND price > 0
|
|
75
|
+
""".format(table), [item.ID])
|
|
76
|
+
prices = [ row[0] for row in cur ]
|
|
77
|
+
if not prices:
|
|
78
|
+
if tdb.tdenv.detail:
|
|
79
|
+
tdb.tdenv.NOTE("{}: Zero entries", item.dbname)
|
|
80
|
+
continue
|
|
81
|
+
prices.sort()
|
|
82
|
+
numPrices = len(prices)
|
|
83
|
+
if numPrices < 20:
|
|
84
|
+
tdb.tdenv.NOTE("{}: only {} rows", item.dbname, numPrices)
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
lowPos = int(numPrices * lowP)
|
|
88
|
+
midPos = int(numPrices * 0.5)
|
|
89
|
+
highPos = int(numPrices * highP)
|
|
90
|
+
|
|
91
|
+
low = prices[lowPos]
|
|
92
|
+
mid = prices[midPos]
|
|
93
|
+
high = prices[highPos]
|
|
94
|
+
|
|
95
|
+
avg = int(sum(prices) / numPrices)
|
|
96
|
+
midlist = prices[lowPos:highPos]
|
|
97
|
+
midavg = int(sum(midlist) / len(midlist))
|
|
98
|
+
|
|
99
|
+
# project the line from p50->p5 to predict
|
|
100
|
+
# what we would expect p0 to be.
|
|
101
|
+
# prices under 11 are invalid anyway
|
|
102
|
+
bestMid = max(mid, avg)
|
|
103
|
+
leastMid = min(mid, avg)
|
|
104
|
+
lowCutoff = max(int(leastMid - (bestMid - low) * multiplier), 0)
|
|
105
|
+
highCutoff = int(bestMid + (high - leastMid) * multiplier)
|
|
106
|
+
|
|
107
|
+
if prices[0] < 11:
|
|
108
|
+
alert = colorama.Fore.RED
|
|
109
|
+
comp, cutoff, error = '<', 11, 'DUMB'
|
|
110
|
+
elif prices[0] < lowCutoff:
|
|
111
|
+
alert = colorama.Fore.YELLOW
|
|
112
|
+
comp, cutoff, error = '<', lowCutoff, 'LOW'
|
|
113
|
+
elif prices[-1] > 100 and prices[-1] > highCutoff:
|
|
114
|
+
alert = colorama.Fore.CYAN
|
|
115
|
+
comp, cutoff, error = '>', highCutoff, 'HIGH'
|
|
116
|
+
else:
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
if errorFilter and error != errorFilter:
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
count = remediation(item, comp, cutoff)
|
|
123
|
+
print(
|
|
124
|
+
alert,
|
|
125
|
+
mask.format(
|
|
126
|
+
numPrices,
|
|
127
|
+
item.dbname,
|
|
128
|
+
prices[0],
|
|
129
|
+
lowCutoff,
|
|
130
|
+
low,
|
|
131
|
+
mid,
|
|
132
|
+
avg,
|
|
133
|
+
midavg,
|
|
134
|
+
high,
|
|
135
|
+
highCutoff,
|
|
136
|
+
prices[-1],
|
|
137
|
+
'{:<4s} {:>4n}'.format(error, count),
|
|
138
|
+
),
|
|
139
|
+
colorama.Fore.RESET,
|
|
140
|
+
sep="",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
if stations:
|
|
144
|
+
print()
|
|
145
|
+
print("Generating", deletePrices)
|
|
146
|
+
now = tdb.query("SELECT CURRENT_TIMESTAMP").fetchone()[0]
|
|
147
|
+
with open(deletePrices, "w", encoding="utf-8") as fh:
|
|
148
|
+
print("# Deletions based on {} prices".format(
|
|
149
|
+
table,
|
|
150
|
+
), file=fh)
|
|
151
|
+
for stnID, items in stations.items():
|
|
152
|
+
station = tdb.stationByID[stnID]
|
|
153
|
+
print(file=fh)
|
|
154
|
+
print("@ {}".format(station.name()), file=fh)
|
|
155
|
+
for item in items:
|
|
156
|
+
itemID = item[0]
|
|
157
|
+
print(" {:<30} {:>7} {:>7} {:>9} {:>9} {} # was {} {}"
|
|
158
|
+
.format(
|
|
159
|
+
tdb.itemByID[itemID].name(),
|
|
160
|
+
0, 0,
|
|
161
|
+
'-', '-',
|
|
162
|
+
now,
|
|
163
|
+
item[1], item[2],
|
|
164
|
+
), file=fh
|
|
165
|
+
)
|
|
166
|
+
if doDeletions:
|
|
167
|
+
db = tdb.getDB()
|
|
168
|
+
print("Generating", deleteSql)
|
|
169
|
+
with open(deleteSql, "w", encoding="utf-8") as fh:
|
|
170
|
+
for deletion in deletions:
|
|
171
|
+
print(deletion[0], '; --', deletion[1], file=fh)
|
|
172
|
+
if doDeletions:
|
|
173
|
+
db.execute(deletion[0])
|
|
174
|
+
if doDeletions:
|
|
175
|
+
db.commit()
|
|
176
|
+
|
|
177
|
+
def main():
|
|
178
|
+
parser = argparse.ArgumentParser(
|
|
179
|
+
description='Check for prices that are outside reasonable bounds.'
|
|
180
|
+
)
|
|
181
|
+
parser.add_argument(
|
|
182
|
+
'--percentile',
|
|
183
|
+
help='Set cutoff percentile',
|
|
184
|
+
type=float,
|
|
185
|
+
default=2,
|
|
186
|
+
)
|
|
187
|
+
parser.add_argument(
|
|
188
|
+
'--selling',
|
|
189
|
+
help='Check the StationSelling table instead of StationBuying',
|
|
190
|
+
action='store_true',
|
|
191
|
+
)
|
|
192
|
+
parser.add_argument(
|
|
193
|
+
'--delete',
|
|
194
|
+
help='Remove bad elements from the local .db immediately',
|
|
195
|
+
action='store_true',
|
|
196
|
+
default=False,
|
|
197
|
+
)
|
|
198
|
+
parser.add_argument(
|
|
199
|
+
'--db',
|
|
200
|
+
help='Specify location of the SQLite database.',
|
|
201
|
+
default=None,
|
|
202
|
+
dest='dbFilename',
|
|
203
|
+
type=str,
|
|
204
|
+
)
|
|
205
|
+
parser.add_argument(
|
|
206
|
+
'--debug', '-w',
|
|
207
|
+
help='Enable/raise level of diagnostic output.',
|
|
208
|
+
default=0,
|
|
209
|
+
required=False,
|
|
210
|
+
action='count',
|
|
211
|
+
)
|
|
212
|
+
parser.add_argument(
|
|
213
|
+
'--detail', '-v',
|
|
214
|
+
help='Increase level of detail in output.',
|
|
215
|
+
default=0,
|
|
216
|
+
required=False,
|
|
217
|
+
action='count',
|
|
218
|
+
)
|
|
219
|
+
parser.add_argument(
|
|
220
|
+
'--quiet', '-q',
|
|
221
|
+
help='Reduce level of detail in output.',
|
|
222
|
+
default=0,
|
|
223
|
+
required=False,
|
|
224
|
+
action='count',
|
|
225
|
+
)
|
|
226
|
+
parser.add_argument(
|
|
227
|
+
'--margin',
|
|
228
|
+
help='Adjust the error margin.',
|
|
229
|
+
type=int,
|
|
230
|
+
default=25,
|
|
231
|
+
)
|
|
232
|
+
parser.add_argument(
|
|
233
|
+
'--ignore',
|
|
234
|
+
help='Ignore items.',
|
|
235
|
+
action='append',
|
|
236
|
+
default=[],
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
filters = parser.add_mutually_exclusive_group()
|
|
240
|
+
filters.add_argument(
|
|
241
|
+
'--dumb',
|
|
242
|
+
help='Limit to "DUMB" items (<11cr).',
|
|
243
|
+
dest='filters',
|
|
244
|
+
action='store_const',
|
|
245
|
+
const='DUMB',
|
|
246
|
+
)
|
|
247
|
+
filters.add_argument(
|
|
248
|
+
'--high',
|
|
249
|
+
help='Limit to "HIGH" items.',
|
|
250
|
+
dest='filters',
|
|
251
|
+
action='store_const',
|
|
252
|
+
const='HIGH',
|
|
253
|
+
)
|
|
254
|
+
filters.add_argument(
|
|
255
|
+
'--low',
|
|
256
|
+
help='Limit to "LOW" items.',
|
|
257
|
+
dest='filters',
|
|
258
|
+
action='store_const',
|
|
259
|
+
const='LOW',
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
argv = parser.parse_args(sys.argv[1:])
|
|
263
|
+
argv.ignore = [
|
|
264
|
+
ignore.upper() for ignore in argv.ignore
|
|
265
|
+
]
|
|
266
|
+
|
|
267
|
+
table = "StationSelling" if argv.selling else "StationBuying"
|
|
268
|
+
tdenv = tradeenv.TradeEnv(properties=argv)
|
|
269
|
+
tdb = tradedb.TradeDB(tdenv)
|
|
270
|
+
|
|
271
|
+
tdenv.NOTE(
|
|
272
|
+
"Checking {}, margin={}", table, argv.margin,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
errorFilter = getattr(argv, "filters", None)
|
|
276
|
+
check_price_bounds(
|
|
277
|
+
tdb,
|
|
278
|
+
table,
|
|
279
|
+
margin=argv.margin,
|
|
280
|
+
doDeletions=argv.delete,
|
|
281
|
+
percentile=argv.percentile,
|
|
282
|
+
errorFilter=errorFilter,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
if __name__ == "__main__":
|
|
287
|
+
main()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Wrapper for trade-dangerous clipboard functionality.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
if 'NOTK' not in os.environ:
|
|
8
|
+
try:
|
|
9
|
+
from tkinter import Tk
|
|
10
|
+
|
|
11
|
+
class SystemNameClip:
|
|
12
|
+
"""
|
|
13
|
+
A cross-platform wrapper for copying system names into
|
|
14
|
+
the clipboard such that they can be pasted into the E:D
|
|
15
|
+
galaxy search - which means forcing them into lower case
|
|
16
|
+
because E:D's search doesn't work with upper case
|
|
17
|
+
characters.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self.tkroot = Tk()
|
|
22
|
+
self.tkroot.withdraw()
|
|
23
|
+
|
|
24
|
+
def strip(self, text, trailing):
|
|
25
|
+
if text.endswith(trailing):
|
|
26
|
+
text = text[:-len(trailing)].strip()
|
|
27
|
+
return text
|
|
28
|
+
|
|
29
|
+
def copy_text(self, text):
|
|
30
|
+
""" Copies the specified text into the clipboard. """
|
|
31
|
+
text = text.lower().strip()
|
|
32
|
+
text = self.strip(text, "(fix)")
|
|
33
|
+
text = self.strip(text, "(fixed)")
|
|
34
|
+
self.tkroot.clipboard_clear()
|
|
35
|
+
self.tkroot.clipboard_append(text.lower())
|
|
36
|
+
|
|
37
|
+
except ImportError:
|
|
38
|
+
print(
|
|
39
|
+
"WARNING: This feature expects you to have 'tkinter' package "
|
|
40
|
+
"installed to work. It is either not installed or broken.\n"
|
|
41
|
+
"Set the environment variable 'NOTK' to disable this warning."
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
class SystemNameClip:
|
|
45
|
+
"""
|
|
46
|
+
Dummy implementation when tkinter is not available.
|
|
47
|
+
"""
|
|
48
|
+
def copy_text(self, text):
|
|
49
|
+
pass
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Coordinate ID generator:
|
|
2
|
+
# Takes a set of 3d coordinates (x, y, z), truncates to 2dp and then
|
|
3
|
+
# generates a base64-like(*) representation that can be used as an id
|
|
4
|
+
# for the object.
|
|
5
|
+
#
|
|
6
|
+
# The actual encoding is not base64, since base64 contains characters
|
|
7
|
+
# that present a problem when used, e.g, in URIs. I replaced the '+'
|
|
8
|
+
# and '/' characters with '_' and '.' respectively.
|
|
9
|
+
#
|
|
10
|
+
# Example usage:
|
|
11
|
+
# coord_to_id64(51.5625, 32.1875, -27.125)
|
|
12
|
+
# Generates:
|
|
13
|
+
# "1gA:Oi:-Go"
|
|
14
|
+
#
|
|
15
|
+
# A reversing function is also provided.
|
|
16
|
+
#
|
|
17
|
+
# Original Author: Oliver "kfsone" Smith <oliver@kfs.org>
|
|
18
|
+
# Released under the "use it with attribution" license.
|
|
19
|
+
|
|
20
|
+
import string
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
alphabet = string.digits + string.ascii_lowercase + string.ascii_uppercase + '_.'
|
|
24
|
+
precision = 100.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def coord_to_d64(coord):
|
|
28
|
+
i = int(abs(coord * precision))
|
|
29
|
+
|
|
30
|
+
digits = ""
|
|
31
|
+
while True:
|
|
32
|
+
digits = alphabet[i & 63] + digits
|
|
33
|
+
i >>= 6 # divides by 64, or one digit
|
|
34
|
+
if i == 0:
|
|
35
|
+
break
|
|
36
|
+
|
|
37
|
+
sign = '-' if coord < 0 else ''
|
|
38
|
+
return sign + digits
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def d64_to_coord(d64):
|
|
42
|
+
divisor, digits = precision, d64
|
|
43
|
+
if d64.startswith('-'):
|
|
44
|
+
divisor = -divisor
|
|
45
|
+
digits = digits[1:]
|
|
46
|
+
|
|
47
|
+
number = 0
|
|
48
|
+
for digit in digits:
|
|
49
|
+
value = alphabet.find(digit)
|
|
50
|
+
if value < 0:
|
|
51
|
+
raise ValueError("Invalid d64 value: {}".format(value))
|
|
52
|
+
number = (number * 64) + value
|
|
53
|
+
|
|
54
|
+
return number / divisor
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def pos_to_id64(x, y, z):
|
|
58
|
+
return coord_to_d64(x) + ':' + coord_to_d64(y) + ':' + coord_to_d64(z)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def id64_to_pos(id64):
|
|
62
|
+
(x64, y64, z64) = id64.split(':')
|
|
63
|
+
return (d64_to_coord(x64), d64_to_coord(y64), d64_to_coord(z64))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
test1 = ( 51.5625,32.1875,-27.125 )
|
|
68
|
+
test2 = ( -154.65625,40.34375,-82.78125 )
|
|
69
|
+
|
|
70
|
+
id64 = pos_to_id64(test1[0], test1[1], test1[2])
|
|
71
|
+
print("id64 of {} = {}".format(test1, id64))
|
|
72
|
+
pos = id64_to_pos(id64)
|
|
73
|
+
print("pos of {} = {}".format(id64, pos))
|
|
74
|
+
|
|
75
|
+
id64 = pos_to_id64(test1[0], test1[1], test1[2])
|
|
76
|
+
print("id64 of {} = {}".format(test1, id64))
|
|
77
|
+
pos = id64_to_pos(id64)
|
|
78
|
+
print("pos of {} = {}".format(id64, pos))
|
|
79
|
+
|
|
80
|
+
id64 = pos_to_id64(test2[0], test2[1], test2[2])
|
|
81
|
+
print("id64 of {} = {}".format(test2, id64))
|
|
82
|
+
pos = id64_to_pos(id64)
|
|
83
|
+
print("pos of {} = {}".format(id64, pos))
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CSVDialect:
|
|
5
|
+
"""
|
|
6
|
+
Defines the TDCSVDialect class for fine-tuning CSV parsing.
|
|
7
|
+
|
|
8
|
+
Python's CSV parser uses an Excel dialect for CSV parsing which
|
|
9
|
+
seems to introduce some needless overhead parsing our simpler,
|
|
10
|
+
standard format.
|
|
11
|
+
|
|
12
|
+
Providing the correct defaults trims away a little inefficiency,
|
|
13
|
+
but it may also protect us against any opinionated future
|
|
14
|
+
changes to the python defaults.
|
|
15
|
+
|
|
16
|
+
In particular, the current approach causes the CSV parser to
|
|
17
|
+
produce quoted strings with their quotes intact:
|
|
18
|
+
|
|
19
|
+
csv.reader("'hello'")
|
|
20
|
+
-> "'hello'"
|
|
21
|
+
csv.reader("'hello'", dialect=CSVDialect)
|
|
22
|
+
-> "hello"
|
|
23
|
+
|
|
24
|
+
Use:
|
|
25
|
+
|
|
26
|
+
import csv
|
|
27
|
+
from tradedangerous.misc.csvdialect import CSVDialect
|
|
28
|
+
|
|
29
|
+
old_style = csv.reader(open("data/System.csv", encoding="utf-8"))
|
|
30
|
+
new_style = csv.reader(open("data/System.csv", encoding="utf-8"),
|
|
31
|
+
dialect=CSVDialect)
|
|
32
|
+
|
|
33
|
+
print("headers:")
|
|
34
|
+
print("- old:", next(old_style))
|
|
35
|
+
print("- new:", next(new_style)) # no difference
|
|
36
|
+
|
|
37
|
+
for i, (old, new) in enumerate(zip(old_style, new_style)):
|
|
38
|
+
print(f"{i}: ids={old[0]},{new[0]}; names={old[1]},{new[1]}")
|
|
39
|
+
if i >= 5:
|
|
40
|
+
break
|
|
41
|
+
"""
|
|
42
|
+
# comma separator, not tab - it's what the 'c' stands for, damnit Excel.
|
|
43
|
+
delimiter = ","
|
|
44
|
+
# single-quote for non-numeric values
|
|
45
|
+
quotechar = "'"
|
|
46
|
+
# The only escape we support is '' within a quoted string for a literal single-quote
|
|
47
|
+
# ... they term that ... *drum roll* double quote.
|
|
48
|
+
escapechar = None
|
|
49
|
+
doublequote = True # ... I'm not making this up
|
|
50
|
+
# Unix-style line endings, no old-mac LFCR and no old-dos CRLF please.
|
|
51
|
+
lineterminator = "\n"
|
|
52
|
+
# No whitespace chasing
|
|
53
|
+
skipinitialspace = False
|
|
54
|
+
# We actually expect all non-numerics to be quoted, such as the empty string,
|
|
55
|
+
# but we're happy to tolerate less. Also, if we use the slightly more aggressive
|
|
56
|
+
# quoting, it requires floats to be quoted ... which seems insane.
|
|
57
|
+
quoting = csv.QUOTE_MINIMAL
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import tradedb
|
|
4
|
+
tdb = tradedb.TradeDB()
|
|
5
|
+
|
|
6
|
+
names = set()
|
|
7
|
+
|
|
8
|
+
for sys in tdb.systemByID.values():
|
|
9
|
+
for stn in sys.stations:
|
|
10
|
+
names.add(stn.name().upper())
|
|
11
|
+
|
|
12
|
+
mutators = {
|
|
13
|
+
'D': [ 'O', '0', ],
|
|
14
|
+
'W': [ 'VV', ],
|
|
15
|
+
'R': [ 'IT' ],
|
|
16
|
+
'L': [ 'II' ],
|
|
17
|
+
'&': [ '6' ],
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
def mutate(text, pos):
|
|
21
|
+
for i in range(pos, len(text)):
|
|
22
|
+
char = text[i]
|
|
23
|
+
if char not in mutators:
|
|
24
|
+
continue
|
|
25
|
+
bef, aft = text[:i], text[i+1:]
|
|
26
|
+
for mutant in mutators[char]:
|
|
27
|
+
t2 = bef + mutant + aft
|
|
28
|
+
yield t2
|
|
29
|
+
yield from mutate(str(t2), i+len(mutant))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
for name in names:
|
|
33
|
+
for mutant in mutate(name, 0):
|
|
34
|
+
if mutant in names:
|
|
35
|
+
print("{} <-> {}".format(name, mutant))
|