diff --git a/.gitignore b/.gitignore index 93e775de..50bc499e 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,6 @@ data/TradeDangerous.db data/TradeDangerous.db-journal data/TradeDangerous.prices *.prices -TradeDangerous.sln *.suo *.pyperf misc/*.csv diff --git a/CHANGES.txt b/CHANGES.txt index 1089775b..ec88e813 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -3,8 +3,15 @@ TradeDangerous, Copyright (C) Oliver "kfsone" Smith, July 2014 ============================================================================== v6.2.1 [wip] +. (kfsone) "buy" and "sell" --near now checks the station/system too +. (kfsone) "buy" now shows average cost if you specify --detail (-v) +. (kfsone) "sell" now shows average value if you specify --detail (-v) +. (kfsone) Fixed item name matching (--avoid) +. (kfsone) Fixed use of via in "run" . (kfsone) Exposed cache.regeneratePricesFile() . (kfsone) Call regeneratePricesFile() after calling plugin.finish() +. (kfsone) General code cleanup (removed buildLinks and loadTrades) +. (kfsone) Added VisualStudio pyproj (great for performance analysis) v6.2.0 Dec 11 2014 . (kfsone) Added plugin system for "import", added a maddavo plugin. diff --git a/README.txt b/README.txt index 7d411f8b..266a550e 100644 --- a/README.txt +++ b/README.txt @@ -806,12 +806,10 @@ Construction of a wholly-default TradeDB can take a while because it loads a lot of data that you often probably won't need. You can speed it up by disabling the bulk of this with: - tdb = TradeDB(tdenv, buildLinks=False, includeTrades=False) + tdb = TradeDB(tdenv, loadTrades=False) If you subsequently need this data, call - tdb.buildLinks() -or tdb.loadTrades() As of TD 6.0 you should need to load this data less and less. A lot of diff --git a/TradeDangerous.pyproj b/TradeDangerous.pyproj new file mode 100644 index 00000000..a737c181 --- /dev/null +++ b/TradeDangerous.pyproj @@ -0,0 +1,93 @@ + + + + Debug + 2.0 + {d36f3ae1-f917-41e9-a3f9-61cfe02c5f0f} + + trade.py + + . + . + {888888a0-9f3d-457c-b088-3a5042f75d52} + Standard Python launcher + + + run -vv --ly 10.38 --empty 11.16 --cap 18 --jumps 6 --cr 131366 --from "aritimi" -s 3 + False + + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TradeDangerous.sln b/TradeDangerous.sln new file mode 100644 index 00000000..0ad58478 --- /dev/null +++ b/TradeDangerous.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.30501.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "TradeDangerous", "TradeDangerous.pyproj", "{D36F3AE1-F917-41E9-A3F9-61CFE02C5F0F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D36F3AE1-F917-41E9-A3F9-61CFE02C5F0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D36F3AE1-F917-41E9-A3F9-61CFE02C5F0F}.Release|Any CPU.ActiveCfg = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/commands/buy_cmd.py b/commands/buy_cmd.py index 2de98eed..66d8d354 100644 --- a/commands/buy_cmd.py +++ b/commands/buy_cmd.py @@ -65,6 +65,17 @@ def run(results, cmdenv, tdb): item = tdb.lookupItem(cmdenv.item) cmdenv.DEBUG0("Looking up item {} (#{})", item.name(), item.ID) + results.summary = ResultRow() + results.summary.item = item + + if cmdenv.detail: + avgPrice = tdb.query(""" + SELECT CAST(AVG(ss.price) AS INT) + FROM StationSelling AS ss + WHERE ss.item_id = ? + """, [item.ID]).fetchone()[0] + results.summary.avg = avgPrice + # Constraints tables = "StationSelling AS ss" constraints = [ "(item_id = {})".format(item.ID) ] @@ -86,30 +97,29 @@ def run(results, cmdenv, tdb): else: columns.append('0') - results.summary = ResultRow() - results.summary.item = item - nearSystem = cmdenv.nearSystem - distances = dict() if nearSystem: maxLy = cmdenv.maxLyPer or tdb.maxSystemLinkLy results.summary.near = nearSystem results.summary.ly = maxLy cmdenv.DEBUG0("Searching within {}ly of {}", maxLy, nearSystem.name()) + systemRanges = { + system: dist + for system, dist in tdb.genSystemsInRange( + nearSystem, + maxLy, + includeSelf=True, + ) + } tables += ( - " INNER JOIN StationLink AS sl" - " ON (sl.rhs_station_id = ss.station_id)" - ) - columns.append('sl.dist') - constraints.append("(lhs_system_id = {})".format( - nearSystem.ID - )) - constraints.append("(dist <= {})".format( - maxLy - )) - else: - columns.append('0') + " INNER JOIN Station AS stn" + " ON (stn.station_id = ss.station_id)" + ) + constraints.append("(stn.system_id IN ({}))".format( + ",".join(['?'] * len(systemRanges)) + )) + bindValues += list(system.ID for system in systemRanges.keys()) whereClause = ' AND '.join(constraints) stmt = """ @@ -125,12 +135,12 @@ def run(results, cmdenv, tdb): cur = tdb.query(stmt, bindValues) stationByID = tdb.stationByID - for (stationID, priceCr, stock, age, dist) in cur: + for (stationID, priceCr, stock, age) in cur: row = ResultRow() row.station = stationByID[stationID] cmdenv.DEBUG2("{} {}cr {} units", row.station.name(), priceCr, stock) if nearSystem: - row.dist = dist + row.dist = systemRanges[row.station.system] row.price = priceCr row.stock = stock row.age = age @@ -188,3 +198,10 @@ def render(results, cmdenv, tdb): for row in results.rows: print(stnRowFmt.format(row)) + + if cmdenv.detail: + print("{:{lnl}} {:>10n}".format( + "-- Average", + results.summary.avg, + lnl=longestNameLen, + )) diff --git a/commands/commandenv.py b/commands/commandenv.py index 2faf376c..5f1c737a 100644 --- a/commands/commandenv.py +++ b/commands/commandenv.py @@ -163,7 +163,7 @@ def checkAvoids(self): # individually. for avoid in ','.join(avoidances).split(','): # Is it an item? - item, system, station = None, None, None + item, place = None, None try: item = tdb.lookupItem(avoid) avoidItems.append(item) @@ -197,11 +197,11 @@ def checkAvoids(self): def checkVias(self): """ Process a list of station names and build them into a list of waypoints. """ - viaStationNames = getattr(self, 'via', None) - viaStations = self.viaStations = [] + viaPlaceNames = getattr(self, 'via', None) + viaPlaces = self.viaPlaces = [] # accept [ "a", "b,c", "d" ] by joining everything and then splitting it. - if viaStationNames: - for via in ",".join(viaStationNames).split(","): - viaStations.append(self.tdb.lookupStation(via)) + if viaPlaceNames: + for via in ",".join(viaPlaceNames).split(","): + viaPlaces.append(self.tdb.lookupPlace(via)) diff --git a/commands/nav_cmd.py b/commands/nav_cmd.py index cec7fad1..09d5e7d6 100644 --- a/commands/nav_cmd.py +++ b/commands/nav_cmd.py @@ -22,12 +22,6 @@ metavar='N.NN', type=float, ), - ParseArgument('--aggressive', - help='Try more aggressively.', - action='count', - dest='aggressiveness', - default=0, - ), ParseArgument('--avoid', help='Exclude a system from the route. If you specify a station, ' 'the system that station is in will be avoided instead.', @@ -87,12 +81,7 @@ def getRoute(cmdenv, tdb, srcSystem, dstSystem, maxLyPer): cmdenv.DEBUG0("Avoiding: {}", list(avoiding)) # As long as the open list is not empty, keep iterating. - overshoot = (cmdenv.aggressiveness * 4) + 1 - while openList: - if dstSystem in distances: - overshoot -= 1 - if overshoot == 0: - break + while openList and dstSystem not in distances: # Expand the search domain by one jump; grab the list of # nodes that are this many hops out and then clear the list. diff --git a/commands/run_cmd.py b/commands/run_cmd.py index 907d7142..1b8757d2 100644 --- a/commands/run_cmd.py +++ b/commands/run_cmd.py @@ -326,13 +326,18 @@ def validateRunArguments(tdb, cmdenv): if cmdenv.startStation == cmdenv.stopStation: raise CommandLineError("Same to/from; more than one hop required.") - viaSet = cmdenv.viaSet = set(cmdenv.viaStations) + viaSet = cmdenv.viaSet = set(cmdenv.viaPlaces) + viaSystems = set() for place in viaSet: - if isinstance(place, Station) and not place.itemCount: - raise NoDataError( - "No price data available for via station {}.".format( - place.name() - )) + if isinstance(place, Station): + if not place.itemCount: + raise NoDataError( + "No price data available for via station {}.".format( + place.name() + )) + viaSystems.add(place.system) + else: + viaSystems.add(place) # How many of the hops do not have pre-determined stations. For example, # when the user uses "--from", they pre-determine the starting station. @@ -343,7 +348,7 @@ def validateRunArguments(tdb, cmdenv): fixedRoutePoints += 1 totalRoutePoints = cmdenv.hops + 1 adhocRoutePoints = totalRoutePoints - fixedRoutePoints - if len(viaSet) > adhocRoutePoints: + if len(viaSystems) > adhocRoutePoints: raise CommandLineError( "Route is not long enough for the list of '--via' " "destinations you gave. Reduce the vias or try again " @@ -376,8 +381,8 @@ def validateRunArguments(tdb, cmdenv): if cmdenv.unique and cmdenv.hops >= len(tdb.stationByID): raise CommandLineError("Requested unique trip with more hops than there are stations...") if cmdenv.unique: - startConflict = (startStn and (startStn == stop or startStn in viaSet)) - stopConflict = (stop and stop in viaSet) + startConflict = (startStn and (startStn == stopStn or startStn in viaSet)) + stopConflict = (stopStn and stopStn in viaSet) if startConflict or stopConflict: raise CommandLineError("from/to/via repeat conflicts with --unique") @@ -408,6 +413,21 @@ def validateRunArguments(tdb, cmdenv): raise NoDataError("No data found for potential buyers for items from {}.".format( startStn.name())) + +###################################################################### + + +def filterByVia(routes, viaSet, viaStartPos): + matchedRoutes = list(routes) + for route in routes: + met = 0 + for hop in route.route[viaStartPos:]: + if hop in viaSet or hop.system in viaSet: + met += 1 + if met >= len(viaSet): + matchedRoutes.append(route) + return matchedRoutes + ###################################################################### # Perform query and populate result set @@ -420,7 +440,6 @@ def run(results, cmdenv, tdb): raise NoDataError("Database does not contain any profitable trades.") validateRunArguments(tdb, cmdenv) - tdb.buildLinks() from tradecalc import TradeCalc, Route @@ -483,15 +502,14 @@ def run(results, cmdenv, tdb): ### routes that include a, b or c. On hop 4, only include routes that ### already include 2 of the vias, on hop 5, require all 3. if viaSet: - routes = [ route for route in routes if viaSet & set(route.route[viaStartPos:]) ] - elif cmdenv.adhocHops == len(viaSet): - # Everywhere we're going is in the viaSet. + routes = filterByVia(routes, viaSet, viaStartPos) + elif len(viaSet) > cmdenv.adhocHops: restrictTo = viaSet routes = calc.getBestHops(routes, restrictTo=restrictTo) if viaSet: - routes = [ route for route in routes if viaSet & set(route.route[viaStartPos:]) ] + routes = filterByVia(routes, viaSet, viaStartPos) if not routes: raise NoDataError("No profitable trades matched your critera, or price data along the route is missing.") diff --git a/commands/sell_cmd.py b/commands/sell_cmd.py index 199a07d5..99fc2093 100644 --- a/commands/sell_cmd.py +++ b/commands/sell_cmd.py @@ -47,6 +47,17 @@ def run(results, cmdenv, tdb): item = tdb.lookupItem(cmdenv.item) cmdenv.DEBUG0("Looking up item {} (#{})", item.name(), item.ID) + results.summary = ResultRow() + results.summary.item = item + + if cmdenv.detail: + avgPrice = tdb.query(""" + SELECT CAST(AVG(sb.price) AS INT) + FROM StationSelling AS sb + WHERE sb.item_id = ? + """, [item.ID]).fetchone()[0] + results.summary.avg = avgPrice + # Constraints tables = "StationBuying AS sb" constraints = [ "(item_id = {})".format(item.ID) ] @@ -57,30 +68,29 @@ def run(results, cmdenv, tdb): constraints.append("(units = -1 or units >= ?)") bindValues.append(cmdenv.quantity) - results.summary = ResultRow() - results.summary.item = item - nearSystem = cmdenv.nearSystem - distances = dict() if nearSystem: maxLy = cmdenv.maxLyPer or tdb.maxSystemLinkLy results.summary.near = nearSystem results.summary.ly = maxLy cmdenv.DEBUG0("Searching within {}ly of {}", maxLy, nearSystem.name()) + systemRanges = { + system: dist + for system, dist in tdb.genSystemsInRange( + nearSystem, + maxLy, + includeSelf=True, + ) + } tables += ( - " INNER JOIN StationLink AS sl" - " ON (sl.rhs_station_id = sb.station_id)" - ) - columns.append('sl.dist') - constraints.append("(lhs_system_id = {})".format( - nearSystem.ID - )) - constraints.append("(dist <= {})".format( - maxLy - )) - else: - columns += [ '0' ] + " INNER JOIN Station AS stn" + " ON (stn.station_id = sb.station_id)" + ) + constraints.append("(stn.system_id IN ({}))".format( + ",".join(['?'] * len(systemRanges)) + )) + bindValues += list(system.ID for system in systemRanges.keys()) whereClause = ' AND '.join(constraints) stmt = """ @@ -96,12 +106,12 @@ def run(results, cmdenv, tdb): cur = tdb.query(stmt, bindValues) stationByID = tdb.stationByID - for (stationID, priceCr, demand, dist) in cur: + for (stationID, priceCr, demand) in cur: row = ResultRow() row.station = stationByID[stationID] cmdenv.DEBUG2("{} {}cr {} units", row.station.name(), priceCr, demand) if nearSystem: - row.dist = dist + row.dist = systemRanges[row.station.system] row.price = priceCr row.demand = demand results.rows.append(row) @@ -151,3 +161,9 @@ def render(results, cmdenv, tdb): for row in results.rows: print(stnRowFmt.format(row)) + if cmdenv.detail: + print("{:{lnl}} {:>10n}".format( + "-- Average", + results.summary.avg, + lnl=longestNameLen, + )) diff --git a/data/Station.csv b/data/Station.csv index 689081f8..046cd8d6 100644 --- a/data/Station.csv +++ b/data/Station.csv @@ -52,6 +52,11 @@ unq:name@System.system_id,unq:name,ls_from_star 'Abrogo','Laing Hub',0.0 'Acihaut','Cuffey Plant',0.0 'Adenets','Allen Horizons',0.0 +'Adriatikuru','Griffiths Dock',0.0 +'Adriatikuru','Grimwood Gateway',0.0 +'Adriatikuru','Menzies Gateway',0.0 +'Adriatikuru','Morrow Gateway',0.0 +'Adriatikuru','Oliver Market',145.0 'AG+21 1754','Oleskiw Hanger',0.0 'AG+21 1754','Still Horizons',0.0 'Aganippe','Julian Market',0.0 @@ -376,6 +381,7 @@ unq:name@System.system_id,unq:name,ls_from_star 'G 230-27','Laplace Works',0.0 'G 250-34','Cavendish Terminal',0.0 'G 65-9','Marconi Port',0.0 +'Gabjaujavas','Vetulani Installation',1750.0 'GAT 2','Thompson Vision',0.0 'Gatonese','Shavers''S Claim',0.0 'GCRV 4654','Herzefeld Landing',0.0 diff --git a/tradedb.py b/tradedb.py index b58fbc37..2a986db9 100644 --- a/tradedb.py +++ b/tradedb.py @@ -80,7 +80,7 @@ class System(object): and lists which stars it has a direct connection to. Do not use _rangeCache directly, use TradeDB.genSystemsInRange. """ - __slots__ = ('ID', 'dbname', 'posX', 'posY', 'posZ', 'links', 'stations', '_rangeCache') + __slots__ = ('ID', 'dbname', 'posX', 'posY', 'posZ', 'stations', '_rangeCache') class RangeCache(object): """ @@ -92,7 +92,6 @@ def __init__(self): def __init__(self, ID, dbname, posX, posY, posZ): self.ID, self.dbname, self.posX, self.posY, self.posZ = ID, dbname, posX, posY, posZ - self.links = {} self.stations = [] self._rangeCache = None @@ -117,7 +116,9 @@ def str(self): def __repr__(self): - return "System(ID={}, dbname='{}', posX={}, posY={}, posZ={})".format(self.ID, re.escape(self.dbname), self.posX, self.posY, self.posZ) + return "System(ID={},dbname='{}',posX={},posY={},posZ={})".format( + self.ID, re.escape(self.dbname), self.posX, self.posY, self.posZ + ) ###################################################################### @@ -252,7 +253,6 @@ class TradeDB(object): Methods: load - Reloads entire database. CAUTION: Destructive - Orphans existing records you reference. - loadTrades - Reloads just the price data. CAUTION: Destructive - Orphans existing records. lookupSystem - Return a system matching "name" with ambiguity detection. lookupStation - Return a station matching "name" with ambiguity detection. lookupShip - Return a ship matching "name" with ambiguity detection. @@ -272,6 +272,8 @@ class TradeDB(object): 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', '[]()*+-.,{}:' ) + trimTrans = str.maketrans('', '', ' \'') + # The DB cache defaultDB = 'TradeDangerous.db' # File containing SQL to build the DB cache from @@ -297,13 +299,10 @@ class TradeDB(object): def __init__(self, tdenv=None, load=True, - buildLinks=False, - includeTrades=False, debug=None, ): self.conn = None self.cur = None - self.numLinks = None self.tradingCount = None tdenv = tdenv or TradeEnv(debug=(debug or 0)) @@ -321,10 +320,9 @@ def __init__(self, if load: self.reloadCache() - self.load(maxSystemLinkLy=tdenv.maxSystemLinkLy, - buildLinks=buildLinks, - includeTrades=includeTrades, - ) + self.load( + maxSystemLinkLy=tdenv.maxSystemLinkLy, + ) ############################################################ @@ -425,44 +423,6 @@ def _loadSystems(self): self.tdenv.DEBUG1("Loaded {:n} Systems", len(systemByID)) - def buildLinks(self): - """ - Populate the list of reachable systems for every star system. - - Not every system can reach every other, and we use the longest jump - that can be made by a ship to limit how many connections we consider - to be "links". - """ - - assert not self.numLinks - - self.tdenv.DEBUG1("Building trade links") - - stmt = """ - SELECT DISTINCT lhs_system_id, rhs_system_id, dist - FROM StationLink - WHERE dist <= {} AND lhs_system_id != rhs_system_id - ORDER BY lhs_system_id - """.format(self.maxSystemLinkLy) - self.cur.execute(stmt) - systemByID = self.systemByID - lastLhsID, lhsLinks = None, None - self.numLinks = 0 - for lhsID, rhsID, dist in self.cur: - if lhsID != lastLhsID: - lhsLinks = systemByID[lhsID].links - lastLhsID = lhsID - lhsLinks[systemByID[rhsID]] = dist - self.numLinks += 1 - - self.numLinks /= 2 - - self.tdenv.DEBUG1("Number of links between systems: {:n}", self.numLinks) - - if self.tdenv.debug: - self._validate() - - def lookupSystem(self, key): """ Look up a System object by it's name. @@ -647,7 +607,7 @@ def lookup(name, candidates): """ Search candidates for the given name """ normTrans = TradeDB.normalizeTrans - trimTrans = str.maketrans('', '', ' \'') + trimTrans = TradeDB.trimTrans nameNorm = name.translate(normTrans) nameTrimmed = nameNorm.translate(trimTrans) @@ -743,7 +703,10 @@ def lookup(name, candidates): # Nothing matched if not any([exactMatch, closeMatch, wordMatch, anyMatch]): - raise TradeException("Unrecognized place: {}".format(name)) + # Note: this was a TradeException and may need to be again, + # but then we need to catch that error in commandenv + # when we process avoids + raise LookupError("Unrecognized place: {}".format(name)) # More than one match raise AmbiguityError( @@ -1020,52 +983,6 @@ def lookupItem(self, name): ############################################################ # Price data. - def loadTrades(self): - """ - Populate the "Trades" table for stations. - - A trade is a connection between two stations where the SRC station - - NOTE: Trades MUST be loaded such that they are populated into the - lists in descending order of profit (highest profit first) - """ - - if self.numLinks is None: - self.buildLinks() - - self.tdenv.DEBUG1("Loading universal trade data") - - # NOTE: Overconsumption. - # We currently fetch ALL possible trades with no regard for reachability; - # as the database grows this will become problematic and we should switch - # to some form of lazy load - that is, given a star, load all potential - # trades it has within a given ly range (based on a multiple of max-ly and - # max jumps). - stmt = """ - SELECT * - FROM vProfits - ORDER BY src_station_id, dst_station_id, gain DESC - """ - self.cur.execute(stmt) - stations, items = self.stationByID, self.itemByID - self.tradingCount = 0 - - prevSrcStnID, prevDstStnID = None, None - srcStn, dstStn = None, None - tradingWith = None - - for (itemID, srcStnID, dstStnID, srcPriceCr, profit, stock, stockLevel, demand, demandLevel, srcAge, dstAge) in self.cur: - if srcStnID != prevSrcStnID: - srcStn, prevSrcStnID, prevDstStnID = stations[srcStnID], srcStnID, None - assert srcStn.tradingWith is None - srcStn.tradingWith = {} - if dstStnID != prevDstStnID: - dstStn, prevDstStnID = stations[dstStnID], dstStnID - tradingWith = srcStn.tradingWith[dstStn] = [] - self.tradingCount += 1 - tradingWith.append(Trade(items[itemID], itemID, srcPriceCr, profit, stock, stockLevel, demand, demandLevel, srcAge, dstAge)) - - def loadStationTrades(self, fromStationIDs): """ Loads all profitable trades that could be made @@ -1117,13 +1034,13 @@ def getTrades(self, src, dst): return srcStn.tradingWith[dstStn] - def load(self, maxSystemLinkLy=None, buildLinks=True, includeTrades=True): + def load(self, maxSystemLinkLy=None): """ Populate/re-populate this instance of TradeDB with data. WARNING: This will orphan existing records you have taken references to: tdb.load() - x = tdb.lookupStation("Aulin") + x = tdb.lookupPlace("Aulin") tdb.load() # x now points to an orphan Aulin """ @@ -1149,30 +1066,10 @@ def load(self, maxSystemLinkLy=None, buildLinks=True, includeTrades=True): self.maxSystemLinkLy = longestJumper.maxLyEmpty else: self.maxSystemLinkLy = maxSystemLinkLy + self.tdenv.DEBUG2("Max ship jump distance: {} @ {:.02f}", longestJumper.name(), self.maxSystemLinkLy) - if buildLinks: - self.buildLinks() - - if includeTrades: - self.loadTrades() - - - def _validate(self): - # Check that things correctly reference themselves. - # Check that system links are bi-directional - for (name, sys) in self.systemByName.items(): - if not sys.links: - self.tdenv.DEBUG2("NOTE: System '%s' has no links" % name) - if sys in sys.links: - raise ValueError("System %s has a link to itself!" % name) - if name in sys.links: - raise ValueError("System %s's name occurs in sys.links" % name) - for link in sys.links: - if sys not in link.links: - raise ValueError("System %s does not have a reciprocal link in %s's links" % (name, link.str())) - ############################################################ # General purpose static methods. @@ -1194,14 +1091,15 @@ class ListSearchMatch(namedtuple('Match', [ 'key', 'value' ])): pass normTrans = TradeDB.normalizeTrans - needle = lookup.translate(normTrans) + trimTrans = TradeDB.trimTrans + needle = lookup.translate(normTrans).translate(trimTrans) partialMatch, wordMatch = [], [] # make a regex to match whole words wordRe = re.compile(r'\b{}\b'.format(lookup), re.IGNORECASE) # describe a match for entry in values: entryKey = key(entry) - normVal = entryKey.translate(normTrans) + normVal = entryKey.translate(normTrans).translate(trimTrans) if normVal.find(needle) > -1: # If this is an exact match, ignore ambiguities. if len(normVal) == len(needle): @@ -1233,5 +1131,9 @@ def normalizedStr(text): punctuation characters that don't contribute to name uniqueness. NOTE: No-longer removes whitespaces or apostrophes. """ - return text.translate(TradeDB.normalizeTrans) + return text.translate( + TradeDB.normalizeTrans + ).translate( + TradeDB.trimTrans + )