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,183 @@
1
+ """
2
+ tradeorm provides the TradeORM class which uses the application database
3
+ rather than trying to be its own database in its own right like TradeDB.
4
+
5
+ Suggested use:
6
+
7
+ # TradeEnv is optional, it's for controlling environment settings
8
+ # builder-pattern style.
9
+ from tradedangerous import TradeEnv, TradeORM
10
+
11
+ tde = TradeEnv() # debug settings, color, etc...
12
+ tdo = TradeORM(tde) # if not supplied, it will make its own
13
+ """
14
+ from __future__ import annotations
15
+ from pathlib import Path
16
+ import os
17
+ import typing
18
+
19
+ from . import TradeEnv
20
+ from .tradeexcept import AmbiguityError, TradeException, MissingDB, SystemNotStationError
21
+ from .db import (
22
+ orm_models as orm, # type: ignore # so we can access models easily
23
+ make_engine_from_config, # type: ignore
24
+ get_session_factory, # type: ignore
25
+ )
26
+
27
+ if typing.TYPE_CHECKING:
28
+ from .db.engine import sessionmaker, Engine, Session # type: ignore
29
+
30
+
31
+ class TradeORM:
32
+ DEFAULT_PATH = "data"
33
+ DEFAULT_DB = "TradeDangerous.db"
34
+ DB_CONFIG_VAR = "TD_DB_CONFIG"
35
+ DB_CONFIG_FILE = "db_config.ini"
36
+
37
+ data_dir: Path
38
+ db_path: Path
39
+
40
+ engine: Engine
41
+ session_maker: sessionmaker[Session]
42
+ session: Session
43
+
44
+ def __init__(self, *, tdenv: TradeEnv | None = None, debug: int | None = None):
45
+ tdenv = tdenv or TradeEnv(debug=debug or 0)
46
+ self.tdenv = tdenv
47
+
48
+ # Determine where the database should be
49
+ data_dir = tdenv.dataDir or TradeORM.DEFAULT_PATH
50
+ self.data_dir = Path(data_dir)
51
+ tdenv.DEBUG0("data_dir = {}", self.data_dir)
52
+
53
+ # Determine the path to the file itself
54
+ db_path = tdenv.dbFilename or (self.data_dir / TradeORM.DEFAULT_DB)
55
+ self.db_path = Path(db_path)
56
+ tdenv.DEBUG0("db_path = {}", self.db_path)
57
+
58
+ # We need it to exist.
59
+ if not self.db_path.exists():
60
+ raise MissingDB(self.db_path)
61
+
62
+ default_config = self.data_dir / TradeORM.DB_CONFIG_FILE
63
+ db_config = os.environ.get(TradeORM.DB_CONFIG_VAR, default_config)
64
+ tdenv.DEBUG0("db_config = {}", db_config)
65
+
66
+ # Make the database available.
67
+ self.engine = make_engine_from_config(db_config)
68
+
69
+ # The user will expect objects (instances of models) that we return
70
+ # to have the same lifetime as the TradeORM() instance, so we want
71
+ # a main session for things to use and return from.
72
+ #
73
+ # However: we also want them to be able to create transactions, etc
74
+ # so we also make the session-factory available.
75
+ self.session = get_session_factory(self.engine)()
76
+
77
+ def commit(self):
78
+ """ Commit the current transaction state. """
79
+ return self.session.commit()
80
+
81
+ def lookup_station(self, name: str) -> orm.Station | None:
82
+ """ Use the database to lookup a station, which accepts a name that
83
+ is either a unique station name (or partial of one), or in the
84
+ 'system name/station name' component. If the station does not
85
+ match a unique station, raises an AmbiguityError
86
+ """
87
+ if "%" in name:
88
+ raise TradeException("wildcards ('%') are not supported in station names")
89
+ if "/" not in name:
90
+ if (station := self._station_lookup(name, exact=True, partial=False)):
91
+ return station
92
+ if self._system_lookup(name, exact=True, partial=False):
93
+ raise SystemNotStationError(f'"{name}" is a system name, use "/{name}" if you meant it as a station')
94
+ name = "/" + name
95
+ station: orm.Station | None = self.lookup_place(name)
96
+ return station
97
+
98
+ def lookup_system(self, name: str) -> orm.System | None:
99
+ """ Use the database to lookup a system, which accepts a name that
100
+ is either a unique system name (or partial of one), or in the
101
+ 'system name/station name' component. If the system does not
102
+ match a unique system, raises an AmbiguityError
103
+ """
104
+ if "%" in name:
105
+ raise TradeException("wildcards ('%') are not supported in system names")
106
+ system_name, _, _ = name.partition("/")
107
+ if not system_name:
108
+ raise TradeException(f"system name required for system lookup, got {name}")
109
+ result: orm.Station | orm.System | None = self.lookup_place(system_name)
110
+ if isinstance(result, orm.Station):
111
+ return result.system
112
+ return result
113
+
114
+ def lookup_place(self, name: str) -> orm.Station | orm.System | None:
115
+ """ Using a "[<system>]/[<station>]" style name, look up either a Station or a System."""
116
+ if "%" in name:
117
+ raise TradeException("wildcards ('%') are not supported in names")
118
+ sys_name, slashed, stn_name = name.partition("/")
119
+ if not slashed:
120
+ if stn_name:
121
+ station: orm.Station | None = self._station_lookup(stn_name, exact=True, partial=False)
122
+ if station:
123
+ return station
124
+ if sys_name:
125
+ system: orm.System | None = self._system_lookup(sys_name, exact=True, partial=False)
126
+ if system:
127
+ return system
128
+
129
+ if sys_name:
130
+ system = self._system_lookup(sys_name)
131
+ if not system:
132
+ raise TradeException(f"unknown system: {sys_name}")
133
+ if not stn_name:
134
+ return system
135
+
136
+ # Now we match the list of station names for this system.
137
+ stmt = self.session.query(orm.Station).filter(orm.Station.system_id == system.system_id).filter(orm.Station.name == stn_name)
138
+ results = stmt.all()
139
+ if len(results) == 1:
140
+ return results[0]
141
+ stmt = self.session.query(orm.Station).filter(orm.Station.system_id == system.system_id).filter(orm.Station.name.like(f"%{stn_name}%"))
142
+ results = stmt.all()
143
+ if len(results) > 1:
144
+ raise AmbiguityError("Station", stn_name, [s.name for s in results])
145
+ return results[0]
146
+
147
+ station = self._station_lookup(stn_name, exact=False)
148
+ return station
149
+
150
+ def _system_lookup(self, name: str, *, exact: bool = True, partial: bool = True) -> orm.System | None:
151
+ """ Look up a model by exact name match. """
152
+ assert exact or partial, "at least one of exact or partial must be True"
153
+ results: list[orm.System] | None = None
154
+ if exact:
155
+ results = self.session.query(orm.System).filter(orm.System.name == name).all()
156
+ if len(results) == 1:
157
+ partial = False
158
+ if partial:
159
+ like_pattern = f"%{name}%"
160
+ results = self.session.query(orm.System).filter(orm.System.name.like(like_pattern)).all()
161
+
162
+ if not results:
163
+ return None
164
+ if len(results) > 1:
165
+ raise AmbiguityError("System", name, results, key=lambda s: s.dbname())
166
+ return results[0]
167
+
168
+ def _station_lookup(self, name: str, *, exact: bool = True, partial: bool = True) -> orm.Station | None:
169
+ """ Look up a model by exact name match. """
170
+ assert exact or partial, "at least one of exact or partial must be True"
171
+ results: list[orm.Station] | None = None
172
+ if exact:
173
+ results = self.session.query(orm.Station).filter(orm.Station.name == name).all()
174
+ if len(results) == 1:
175
+ partial = False
176
+ if partial:
177
+ like_pattern = f"%{name}%"
178
+ results = self.session.query(orm.Station).filter(orm.Station.name.like(like_pattern)).all()
179
+ if not results:
180
+ return None
181
+ if len(results) > 1:
182
+ raise AmbiguityError("Station", name, results, key=lambda s: s.dbname())
183
+ return results[0]
@@ -0,0 +1,192 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from urllib.parse import urlparse, unquote
5
+
6
+ from .tradeexcept import TradeException
7
+ from .misc import progress as pbar
8
+ from . import fs
9
+
10
+ import json
11
+ import time
12
+ import typing
13
+
14
+ import requests
15
+
16
+
17
+ if typing.TYPE_CHECKING:
18
+ from collections.abc import Callable
19
+ import os # for PathLike
20
+ from .tradeenv import TradeEnv
21
+
22
+
23
+ ######################################################################
24
+ # Helpers
25
+
26
+ class HTTP404(TradeException):
27
+ pass
28
+
29
+
30
+ def makeUnit(value: float) -> str:
31
+ num, unit = split_unit(value)
32
+ return f"{num}{unit}"
33
+
34
+
35
+ def split_unit(value: float) -> tuple[str, str]:
36
+ """
37
+ Split a byte size into a (number_str, unit_str) tuple.
38
+ Used when you need to colour or format the numeric part separately.
39
+
40
+ Example:
41
+ >>> split_unit(30200000)
42
+ ('28.8', 'MB')
43
+ """
44
+ units = ["B", "KB", "MB", "GB", "TB"]
45
+ n = float(value)
46
+ i = 0
47
+ while n >= 1024.0 and i < len(units) - 1:
48
+ n /= 1024.0
49
+ i += 1
50
+ num_str = f"{n:.1f}" if i > 0 else f"{int(n)}"
51
+ return num_str, units[i]
52
+
53
+
54
+ def get_filename_from_url(url: str) -> str:
55
+ """ extracts just the filename from a url. """
56
+ return Path(unquote(urlparse(url).path)).name
57
+
58
+
59
+ def download(
60
+ tdenv: TradeEnv,
61
+ url: str,
62
+ localFile: os.PathLike,
63
+ headers: dict | None = None,
64
+ backup: bool = False,
65
+ shebang: Callable | None = None,
66
+ chunkSize: int = 4096,
67
+ timeout: int = 30,
68
+ *,
69
+ length: int | str | None = None,
70
+ session: requests.Session | None = None,
71
+ ):
72
+ """
73
+ Fetch data from a URL and save the output
74
+ to a local file. Returns the response headers.
75
+
76
+ :param tdenv: TradeEnv we're working under
77
+ :param url: URL we're fetching (http, https or ftp)
78
+ :param localFile: Name of the local file to open.
79
+ :param headers: dict() of additional HTTP headers to send
80
+ :param shebang: function to call on the first line
81
+ """
82
+ tdenv.NOTE("Requesting {}", url)
83
+
84
+ if isinstance(length, str):
85
+ length = int(length)
86
+
87
+ # If the caller provided an existing session stream, use that the fetch the request.
88
+ req = (session or requests).get(url, headers=headers or None, stream=True, timeout=timeout)
89
+ req.raise_for_status()
90
+
91
+ encoding = req.headers.get('content-encoding', 'uncompress')
92
+ content_length = req.headers.get('content-length', length)
93
+ transfer = req.headers.get('transfer-encoding', None)
94
+ if not length and transfer != 'chunked':
95
+ # chunked transfer-encoding doesn't need a content-length
96
+ if content_length is None:
97
+ print(req.headers)
98
+ raise TradeException("Remote server replied with invalid content-length.")
99
+ content_length = int(content_length)
100
+ if content_length <= 0:
101
+ raise TradeException(
102
+ "Remote server gave an empty response. Please try again later."
103
+ )
104
+
105
+ # if the file is being compressed by the server, the headers tell us the
106
+ # length of the compressed data, but in our loop below we will be receiving
107
+ # the uncompressed data, which should be larger, which will cause our
108
+ # download indicators to sit at 100% for a really long time if the file is
109
+ # heavily compressed and large (e.g spansh 1.5gb compressed vs 9GB uncompressed)
110
+ if length is None and encoding == "gzip" and content_length:
111
+ length = content_length * 3
112
+
113
+ if tdenv.detail > 1:
114
+ if length:
115
+ tdenv.NOTE("Downloading {} {}ed data", makeUnit(length), encoding)
116
+ else:
117
+ tdenv.NOTE("Downloading {} {}ed data", transfer, encoding)
118
+ tdenv.DEBUG0(str(req.headers).replace("{", "{{").replace("}", "}}"))
119
+
120
+ actPath = Path(localFile)
121
+ fs.ensurefolder(tdenv.tmpDir)
122
+ tmpPath = Path(tdenv.tmpDir, f"{actPath.name}.dl")
123
+
124
+ fetched = 0
125
+ started = time.time()
126
+ filename = get_filename_from_url(url)
127
+ with pbar.Progress(max_value=length, width=25, prefix=filename, style=pbar.CountingBar, show=not tdenv.quiet) as prog, tmpPath.open("wb") as fh:
128
+ for data in req.iter_content(chunk_size=chunkSize):
129
+ fh.write(data)
130
+ fetched += len(data)
131
+ if shebang:
132
+ bangLine = data.decode().partition("\n")[0]
133
+ tdenv.DEBUG0("Checking shebang of {}", bangLine)
134
+ shebang(bangLine)
135
+ shebang = None
136
+ if prog:
137
+ prog.increment(len(data))
138
+ tdenv.DEBUG0("End of data")
139
+
140
+ if not tdenv.quiet:
141
+ elapsed = (time.time() - started) or 1
142
+ num1, unit1 = split_unit(fetched)
143
+ num2, unit2 = split_unit(fetched / elapsed)
144
+ tdenv.NOTE(
145
+ f"Downloaded [cyan]{num1}[/]{unit1} of {encoding}ed data "
146
+ f"[cyan]{num2}[/]{unit2}/s"
147
+ )
148
+
149
+
150
+ fs.ensurefolder(actPath.parent)
151
+
152
+ # Swap the file into place
153
+ if backup:
154
+ bakPath = Path(localFile + ".bak")
155
+ if bakPath.exists():
156
+ bakPath.unlink()
157
+ if actPath.exists():
158
+ actPath.rename(localFile + ".bak")
159
+ if actPath.exists():
160
+ actPath.unlink()
161
+ tmpPath.rename(actPath)
162
+
163
+ req.close()
164
+ return req.headers
165
+
166
+ def get_json_data(url, *, timeout: int = 90):
167
+ """
168
+ Fetch JSON data from a URL and return the resulting dictionary.
169
+
170
+ Displays a progress bar as it downloads.
171
+ """
172
+
173
+ req = requests.get(url, stream=True, timeout=timeout)
174
+
175
+ totalLength = req.headers.get('content-length')
176
+ if totalLength is None:
177
+ compression = req.headers.get('content-encoding')
178
+ compression = (compression + "'ed") if compression else "uncompressed"
179
+ print("Downloading {}: {}...".format(compression, url))
180
+ jsData = req.content
181
+ else:
182
+ totalLength = int(totalLength)
183
+ filename = get_filename_from_url(url)
184
+ progBar = pbar.Progress(totalLength, 25, prefix=filename)
185
+
186
+ jsData = bytes()
187
+ for data in req.iter_content():
188
+ jsData += data
189
+ progBar.increment(len(data))
190
+ progBar.clear()
191
+
192
+ return json.loads(jsData.decode())
@@ -0,0 +1,243 @@
1
+ # --------------------------------------------------------------------
2
+ # Copyright (C) Oliver 'kfsone' Smith 2014 <oliver@kfs.org>:
3
+ # Copyright (C) Bernd 'Gazelle' Gollesch 2016, 2017
4
+ # Copyright (C) Jonathan 'eyeonus' Jones 2018, 2019
5
+ #
6
+ # You are free to use, redistribute, or even print and eat a copy of
7
+ # this software so long as you include this copyright notice.
8
+ # I guarantee there is at least one bug neither of us knew about.
9
+ # --------------------------------------------------------------------
10
+ # TradeDangerous :: Modules :: Utils
11
+ #
12
+ """This modules contains various utils to be used internally.
13
+ """
14
+
15
+ import re
16
+
17
+ __all__ = ['titleFixup', 'checkForOcrDerp']
18
+
19
+ ocrDerp = re.compile(r"""(
20
+ ^.$ |
21
+ LAN[O0]ING |
22
+ [O0][O0]CK |
23
+ [O0]INEILL |
24
+ AQUIRE[O0] |
25
+ FNT[EF]RPRIS[EF] |
26
+ [EF]NTFRPRIS[EF] |
27
+ [EF]NT[EF]RPRISF |
28
+ [O0](UTT|ALT)[O0]N |
29
+ 8RA[DO0]LEY |
30
+ BRA[O0]LEY |
31
+ LLOY[O0] |
32
+ [O0]RBDAL |
33
+ DRB[O0]DAL |
34
+ [D0]RBITAL |
35
+ REE[O0] |
36
+ \BDOCK$ |
37
+ \BTERMINAL\b |
38
+ \bKID?[O0] |
39
+ \b[O3]E\b |
40
+ \bANDRA[O3]E\b |
41
+ \bAN[O3]RADE\b |
42
+ \bAN[O3]RA[O3]E\b |
43
+ VVELL\b |
44
+ [O0]IRAC\b |
45
+ \bVV |
46
+ \b[O0]ER?\b |
47
+ \b[O0]RAKE |
48
+ HAR[O0](T\b|W[I1L]CK) |
49
+ ACQU[I1L]R[E3][O0] |
50
+ \b[O0]ARK |
51
+ \b[O0]DAM |
52
+ [O0]EPOT |
53
+ \bMERE[O0] |
54
+ \b[O0]ENN?IS |
55
+ \bBRAN[o0] |
56
+ W[O0]{3} |
57
+ GO(D[O0]|[O0]D|[O0][O])ARD |
58
+ GO[DO0]{2}AR[O0] |
59
+ ORB[RH]AL\b |
60
+ \bJOR[O0]A |
61
+ \bST[O0]ART |
62
+ \bQUIMPY |
63
+ \bVAR[O0]E |
64
+ EN[^T]?ERPRISE |
65
+ EN..ERPRISE |
66
+ [E38]NT[E38]F[I1']?PR[I1'][S5][E38] |
67
+ \bMUR[O0]O |
68
+ \bBAR[O0]E |
69
+ \bBALLAR[O0] |
70
+ \b[O0]REYER\b |
71
+ \bEDWAR[O0] |
72
+ \bE[O0]WAR[DO0] |
73
+ III |
74
+ STARON\b |
75
+ \bST.ION\b |
76
+ \bSTION\b |
77
+ \BHANG[EA]R$ |
78
+ ^\S+HUB$ |
79
+ \bLEBEOEV |
80
+ \B(
81
+ BASE |
82
+ ENTE[RP]P[RP]ISE |
83
+ TERMINA(L|II) |
84
+ P(L|II)ANT |
85
+ RELAY |
86
+ ORBITAL |
87
+ PLATFORM |
88
+ COLONY |
89
+ VISION |
90
+ REFINERY
91
+ )$ |
92
+ [O0]RB[I1]DAL |
93
+ [O0]R[DL][I1]TAL |
94
+ \bBRIOGER |
95
+ \bJUOSON |
96
+ LANOER |
97
+ G[O0][O0]([RW]|VV)[O0I]N |
98
+ \bSPE([O0][DO0]|[DO0][O0])ING\b |
99
+ \bARCHIMEOES\b |
100
+ \bH[O0D]L[O0]ING |
101
+ \bM[O0D]HMAN[O0] |
102
+ \b[O0]ANA\b |
103
+ \bALEKSAN[O0]R[O0D]V\b |
104
+ \bCH[0D]MSKY\b |
105
+ \b[O0]IESEL\b |
106
+ [O0]{3} |
107
+ SCHMI[O0]T |
108
+ \bSAUN[O0]ER |
109
+ [O0]IV[E3] |
110
+ VIRIDN$ |
111
+ \bHORI\.ONS |
112
+ C[O0D]+LNY$ |
113
+ \bR[O0]ZH[O0]E[S5]TVENSKY |
114
+ \bRDZH[DO0]ESTVENSKY |
115
+ '' |
116
+ ^[^A-Z0-9] |
117
+ \s{2,} |
118
+ ^OEN |
119
+ ^MCK(EF|FE)\b |
120
+ \bCHAN\s+DLER |
121
+ \b[O0]UMONT |
122
+ \bUN[0O]ER |
123
+ \bSDMM |
124
+ \bREA([O0]D|D[O0]) |
125
+ \bRD[DO0][DO0]EN |
126
+ \bR[O0]([O0]D|D[O0])EN |
127
+ (?<!BR)[O0]ECK$ |
128
+ SE?TTL(FMENT|EMFNT|FMFNT) |
129
+ SE?TTLMENT |
130
+ STTL[E38]?M[E38]?NT |
131
+ S\s?[E38]\s?T\s?T\s?L\s?[E38](\sM|M\s)[E38]\s?N\s?T$ |
132
+ S[E38]TT[38]?L[E38]MNT |
133
+ MARKFT |
134
+ HANGFR |
135
+ CL(EVF|FVE|FVF) |
136
+ \bCRY$ |
137
+ G[E3][D0]RG[E3]LUCA[S5] |
138
+ PLAT[E3]F[D0]RM |
139
+ OCONNOR |
140
+ ` |
141
+ -- |
142
+ \bREILLI\b |
143
+ \bRIN[FC]\b |
144
+ \bOL[E3]ARY |
145
+ \bSATION\b |
146
+ ,\w |
147
+ \bI?NGLY\b |
148
+ \bAU\sL[DO0]\b |
149
+ (^|\s)['.] |
150
+ ^- | -$ |
151
+ \bDREBBFL\b
152
+ \bLEVIE |
153
+ \bRN\b |
154
+ \bH\sUNZIKER |
155
+ \bL[O0D]FTH\sUS |
156
+ \bHORNUCH\b |
157
+ \bKLU\sDZE |
158
+ ^[DR]HN\b |
159
+ SU\sI?RVEY$ |
160
+ H[DO0]L[O0]ING |
161
+ H[D0]LDING |
162
+ M[DO0]HMAN[O0] |
163
+ \bABL\b |
164
+ \bBENNET\b |
165
+ \bHU8\b |
166
+ \sCITV$ |
167
+ \sPIT[VY]$ |
168
+ \bTFR |
169
+ IVII |
170
+ \BINAI$ |
171
+ SET[IT]''LEMEN |
172
+ I'L | R'I | (^|\s)'L | [^Ss]'(?=\s|$) |
173
+ ^I \s (?! [Ss][Oo][Ll][Aa]) |
174
+ \bA7\S |
175
+ \sH\sI?UB$ |
176
+ \bALEDNDRIA\b |
177
+ \sH\sU\sB$ |
178
+ \bC[O0]LCNY\b |
179
+ \bOOCTE\b |
180
+ \bBULGAFIIN\b |
181
+ \bWH\sIEEL |
182
+ \bBR8NNAN\b |
183
+ \b(ID)?ING$ |
184
+ GATEVVAY$ |
185
+ [HI\s]U\sI?B$ |
186
+ CLAI\sI?M$ |
187
+ \bUITY$ |
188
+ \bDING$ |
189
+ BTOP$ |
190
+ B'I'OP$ |
191
+ TRANQUNUTY$ |
192
+ C[O0]LUNY$ |
193
+ \bMAGN\sI?US\b |
194
+ \s-[A-Z] |
195
+ ^NAKAM(\sIU|U\sIR) |
196
+ ^THOM\sI?PSON |
197
+ ^STEPH\sI?ENSON |
198
+ \bCQRK\b |
199
+ ^AN\sI?DREW |
200
+ ^WATSO\sIN |
201
+ ^QSSW |
202
+ ^RTZEN |
203
+ \bI?NAL$ |
204
+ \b''I\b
205
+ )""", flags=re.X)
206
+
207
+
208
+ def titleFixup(text):
209
+ """
210
+ Correct case in a word assuming the presence of titles/surnames,
211
+ including 'McDonald', 'MacNair', 'McKilroy', and cases that
212
+ python's title screws up such as "Smith's".
213
+ """
214
+
215
+ text = text.title()
216
+ text = re.sub(
217
+ r"\b(Mc)([a-z])",
218
+ lambda match: match.group(1) + match.group(2).upper(),
219
+ text
220
+ )
221
+ text = re.sub(
222
+ r"\b(Mac)([bcdfgjklmnpqrstvwxyz])([a-z]{3,})",
223
+ lambda m: m.group(1) + m.group(2).upper() + m.group(3),
224
+ text
225
+ )
226
+ text = re.sub("\b(von|van|de|du|of)\b",
227
+ lambda m: m.group(1).lower,
228
+ text
229
+ )
230
+ text = re.sub(r"'S\b", "'s", text)
231
+ text = ''.join((text[0].upper(), text[1:]))
232
+
233
+ return text
234
+
235
+ def checkForOcrDerp(tdenv, systemName, stationName):
236
+ match = ocrDerp.search(stationName.upper())
237
+ if match:
238
+ tdenv.NOTE(
239
+ "Ignoring '{}/{}' because it looks like OCR derp."
240
+ .format(systemName, stationName)
241
+ )
242
+ return match
243
+ return None
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env python3
2
+ # --------------------------------------------------------------------
3
+ # Copyright (C) Oliver 'kfsone' Smith 2014 <oliver@kfs.org>:
4
+ # Copyright (C) Bernd 'Gazelle' Gollesch 2016, 2017
5
+ # Copyright (C) Jonathan 'eyeonus' Jones 2018, 2019
6
+ #
7
+ # You are free to use, redistribute, or even print and eat a copy of
8
+ # this software so long as you include this copyright notice.
9
+ # I guarantee there is at least one bug neither of us knew about.
10
+ # --------------------------------------------------------------------
11
+
12
+ """just keeper of current version"""
13
+
14
+ # TODO: remember to update tests when version changes
15
+ __version__ = '12.7.6'
16
+