def run(results, cmdenv, tdb):
    """
    Fetch all the data needed to display the results of a "rares"
    command. Does not actually print anything.

    Command execution is broken into two steps:
        1. cmd.run(results, cmdenv, tdb)
            Gather all the data required but generate no output,
        2. cmd.render(results, cmdenv, tdb)
            Print output to the user.

    This separation of concerns allows modularity; you can write
    a command that calls another command to fetch data for you
    and knowing it doesn't generate any output. Then you can
    process the data and return it and let the command parser
    decide when to turn it into output.

    It also opens a future door to commands that can present
    their data in a GUI as well as the command line by having
    a custom render() function.

    Parameters:
        results
            An object to be populated and returned
        cmdenv
            A CommandEnv object populated with the parameters
            for the command.
        tdb
            A TradeDB object to query against.

    Returns:
        None
            End execution without any output
        results
            Proceed to "render" with the output.
    """

    # Lookup the system we're currently in.
    start = cmdenv.nearSystem
    # Hoist the padSize parameter for convenience
    padSize = cmdenv.padSize
    # How far we're want to cast our net.
    maxLy = float(cmdenv.maxLyPer or 0.0)

    if cmdenv.illegal:
        wantIllegality = "Y"
    elif cmdenv.legal:
        wantIllegality = "N"
    else:
        wantIllegality = "YN?"

    awaySystems = set()
    if cmdenv.away or cmdenv.awayFrom:
        if not cmdenv.away or not cmdenv.awayFrom:
            raise CommandLineError("Invalid --away/--from usage. See --help")
        minAwayDist = cmdenv.away
        for sysName in cmdenv.awayFrom:
            system = tdb.lookupPlace(sysName).system
            awaySystems.add(system)

    # Start to build up the results data.
    results.summary = ResultRow()
    results.summary.near = start
    results.summary.ly = maxLy
    results.summary.awaySystems = awaySystems

    distCheckFn = start.distanceTo

    # Look through the rares list.
    for rare in tdb.rareItemByID.values():
        if not rare.illegal in wantIllegality:
            continue
        if padSize:  # do we care about pad size?
            if not rare.station.checkPadSize(padSize):
                continue
        rareSys = rare.station.system
        # Find the un-sqrt'd distance to the system.
        dist = distCheckFn(rareSys)
        if 0 < maxLy < dist:
            continue

        if awaySystems:
            awayCheck = rareSys.distanceTo
            if any(awayCheck(away) < minAwayDist for away in awaySystems):
                continue

        # Create a row for this item
        row = ResultRow()
        row.rare = rare
        row.dist = dist
        results.rows.append(row)

    # Was anything matched?
    if not results:
        print("No matches found.")
        return None

    if cmdenv.sortByPrice:
        results.rows.sort(key=lambda row: row.dist)
        results.rows.sort(key=lambda row: row.rare.costCr, reverse=True)
    else:
        results.rows.sort(key=lambda row: row.rare.costCr, reverse=True)
        results.rows.sort(key=lambda row: row.dist)

    if cmdenv.reverse:
        results.rows.reverse()

    limit = cmdenv.limit or 0
    if limit > 0:
        results.rows = results.rows[:limit]

    return results
Example #2
0
def run(results, cmdenv, tdb):
    from commands.commandenv import ResultRow

    if cmdenv.lt and cmdenv.gt:
        if cmdenv.lt <= cmdenv.gt:
            raise CommandLineError("--gt must be lower than --lt")

    item = tdb.lookupItem(cmdenv.item)
    cmdenv.DEBUG0("Looking up item {} (#{})", item.name(), item.ID)

    avoidSystems = {s for s in cmdenv.avoidPlaces if isinstance(s, System)}
    avoidStations = {s for s in cmdenv.avoidPlaces if isinstance(s, Station)}

    results.summary = ResultRow()
    results.summary.item = item
    results.summary.avoidSystems = avoidSystems
    results.summary.avoidStations = avoidStations

    if cmdenv.detail:
        avgPrice = tdb.query("""
            SELECT AVG(si.demand_price)
              FROM StationItem AS si
             WHERE si.item_id = ? AND si.demand_price > 0
        """, [item.ID]).fetchone()[0]
        results.summary.avg = int(avgPrice)

    # Constraints
    tables = "StationItem AS si"
    constraints = [
        "(item_id = {} AND demand_price > 0)".format(item.ID),
    ]
    columns = [
        'si.station_id',
        'si.demand_price',
        'si.demand_units',
    ]
    bindValues = []

    if cmdenv.demand:
        constraints.append("(demand_units >= ?)")
        bindValues.append(cmdenv.demand)

    if cmdenv.lt:
        constraints.append("(demand_price < ?)")
        bindValues.append(cmdenv.lt)
    if cmdenv.gt:
        constraints.append("(demand_price > ?)")
        bindValues.append(cmdenv.gt)

    nearSystem = cmdenv.nearSystem
    if nearSystem:
        maxLy = cmdenv.maxLyPer or tdb.maxSystemLinkLy
        results.summary.near = nearSystem
        results.summary.ly = maxLy
        distanceFn = nearSystem.distanceTo
    else:
        distanceFn = None

    whereClause = ' AND '.join(constraints)
    stmt = """SELECT DISTINCT {columns} FROM {tables} WHERE {where}""".format(
        columns=','.join(columns),
        tables=tables,
        where=whereClause
    )
    cmdenv.DEBUG0('SQL: {}', stmt)
    cur = tdb.query(stmt, bindValues)

    stationByID = tdb.stationByID
    padSize = cmdenv.padSize
    planetary = cmdenv.planetary
    wantNoPlanet = cmdenv.noPlanet
    wantBlackMarket = cmdenv.blackMarket

    for (stationID, priceCr, demand) in cur:
        station = stationByID[stationID]
        if padSize and not station.checkPadSize(padSize):
            continue
        if planetary and not station.checkPlanetary(planetary):
            continue
        if wantNoPlanet and station.planetary != 'N':
            continue
        if wantBlackMarket and station.blackMarket != 'Y':
            continue
        if station in avoidStations:
            continue
        if station.system in avoidSystems:
            continue

        row = ResultRow()
        row.station = station
        if distanceFn:
            distance = distanceFn(row.station.system)
            if distance > maxLy:
                continue
            row.dist = distance
        row.price = priceCr
        row.demand = demand
        row.age = station.itemDataAgeStr
        results.rows.append(row)

    if not results.rows:
        raise NoDataError("No available items found")

    results.summary.sort = "Price"
    results.rows.sort(key=lambda result: result.demand, reverse=True)
    results.rows.sort(key=lambda result: result.price, reverse=True)
    if nearSystem and not cmdenv.sortByPrice:
        results.summary.sort = "Dist"
        results.rows.sort(key=lambda result: result.dist)

    limit = cmdenv.limit or 0
    if limit > 0:
        results.rows = results.rows[:limit]

    return results
Example #3
0
def run(results, cmdenv, tdb):
    """
    Fetch all the data needed to display the results of a "rares"
    command. Does not actually print anything.

    Command execution is broken into two steps:
        1. cmd.run(results, cmdenv, tdb)
            Gather all the data required but generate no output,
        2. cmd.render(results, cmdenv, tdb)
            Print output to the user.

    This separation of concerns allows modularity; you can write
    a command that calls another command to fetch data for you
    and knowing it doesn't generate any output. Then you can
    process the data and return it and let the command parser
    decide when to turn it into output.

    It also opens a future door to commands that can present
    their data in a GUI as well as the command line by having
    a custom render() function.

    Parameters:
        results
            An object to be populated and returned
        cmdenv
            A CommandEnv object populated with the parameters
            for the command.
        tdb
            A TradeDB object to query against.

    Returns:
        None
            End execution without any output
        results
            Proceed to "render" with the output.
    """

    # Lookup the system we're currently in.
    start = cmdenv.nearSystem
    # Hoist the padSize, noPlanet and planetary parameter for convenience
    padSize = cmdenv.padSize
    noPlanet = cmdenv.noPlanet
    planetary = cmdenv.planetary
    # How far we're want to cast our net.
    maxLy = float(cmdenv.maxLyPer or 0.)

    if cmdenv.illegal:
        wantIllegality = 'Y'
    elif cmdenv.legal:
        wantIllegality = 'N'
    else:
        wantIllegality = 'YN?'

    awaySystems = set()
    if cmdenv.away or cmdenv.awayFrom:
        if not cmdenv.away or not cmdenv.awayFrom:
            raise CommandLineError("Invalid --away/--from usage. See --help")
        minAwayDist = cmdenv.away
        for sysName in cmdenv.awayFrom:
            system = tdb.lookupPlace(sysName).system
            awaySystems.add(system)

    # Start to build up the results data.
    results.summary = ResultRow()
    results.summary.near = start
    results.summary.ly = maxLy
    results.summary.awaySystems = awaySystems

    distCheckFn = start.distanceTo

    # Look through the rares list.
    for rare in tdb.rareItemByID.values():
        if not rare.illegal in wantIllegality:
            continue
        if padSize:  # do we care about pad size?
            if not rare.station.checkPadSize(padSize):
                continue
        if planetary:  # do we care about planetary?
            if not rare.station.checkPlanetary(planetary):
                continue
        if noPlanet and rare.station.planetary != 'N':
            continue
        rareSys = rare.station.system
        # Find the un-sqrt'd distance to the system.
        dist = distCheckFn(rareSys)
        if maxLy > 0. and dist > maxLy:
            continue

        if awaySystems:
            awayCheck = rareSys.distanceTo
            if any(awayCheck(away) < minAwayDist for away in awaySystems):
                continue

        # Create a row for this item
        row = ResultRow()
        row.rare = rare
        row.dist = dist
        results.rows.append(row)

    # Was anything matched?
    if not results:
        print("No matches found.")
        return None

    if cmdenv.sortByPrice:
        results.rows.sort(key=lambda row: row.dist)
        results.rows.sort(key=lambda row: row.rare.costCr, reverse=True)
    else:
        results.rows.sort(key=lambda row: row.rare.costCr, reverse=True)
        results.rows.sort(key=lambda row: row.dist)

    if cmdenv.reverse:
        results.rows.reverse()

    limit = cmdenv.limit or 0
    if limit > 0:
        results.rows = results.rows[:limit]

    return results
Example #4
0
def run(results, cmdenv, tdb):
    if cmdenv.lt and cmdenv.gt:
        if cmdenv.lt <= cmdenv.gt:
            raise CommandLineError("--gt must be lower than --lt")

    # Find out what we're looking for.
    queries, mode = get_lookup_list(cmdenv, tdb)
    cmdenv.DEBUG0("{} query: {}", mode, queries.values())

    avoidSystems = {s for s in cmdenv.avoidPlaces if isinstance(s, System)}
    avoidStations = {s for s in cmdenv.avoidPlaces if isinstance(s, Station)}

    # Summarize
    results.summary = ResultRow()
    results.summary.mode = mode
    results.summary.queries = queries
    results.summary.oneStop = cmdenv.oneStop
    results.summary.avoidSystems = avoidSystems
    results.summary.avoidStations = avoidStations

    # In single mode with detail enabled, add average reports.
    # Thus if you're looking up "algae" or the "asp", it'll
    # tell you the average/ship cost.
    singleMode = len(queries) == 1
    if singleMode and cmdenv.detail:
        first = list(queries.values())[0]
        if mode is SHIP_MODE:
            results.summary.avg = first.cost
        else:
            avgPrice = tdb.query(
                """
                SELECT AVG(si.supply_price)
                  FROM StationItem AS si
                 WHERE si.item_id = ? AND si.supply_price > 0
            """, [first.ID]).fetchone()[0]
            if not avgPrice:
                avgPrice = 0
            results.summary.avg = int(avgPrice)

    # System-based search
    nearSystem = cmdenv.nearSystem
    if nearSystem:
        maxLy = cmdenv.maxLyPer or tdb.maxSystemLinkLy
        results.summary.near = nearSystem
        results.summary.ly = maxLy
        distanceFn = nearSystem.distanceTo
    else:
        distanceFn = None

    oneStopMode = cmdenv.oneStop
    padSize = cmdenv.padSize
    planetary = cmdenv.planetary
    wantNoPlanet = cmdenv.noPlanet
    wantBlackMarket = cmdenv.blackMarket

    stations = defaultdict(list)
    stationByID = tdb.stationByID

    cur = sql_query(cmdenv, tdb, queries, mode)
    for (ID, stationID, price, units) in cur:
        station = stationByID[stationID]
        if padSize and not station.checkPadSize(padSize):
            continue
        if planetary and not station.checkPlanetary(planetary):
            continue
        if wantNoPlanet and station.planetary != 'N':
            continue
        if wantBlackMarket and station.blackMarket != 'Y':
            continue
        if station in avoidStations:
            continue
        if station.system in avoidSystems:
            continue

        row = ResultRow()
        row.station = station
        if distanceFn:
            distance = distanceFn(row.station.system)
            if distance > maxLy:
                continue
            row.dist = distance
        row.item = queries[ID]
        row.price = price
        row.units = units
        row.age = station.itemDataAgeStr
        if oneStopMode:
            stationRows = stations[stationID]
            stationRows.append(row)
            if len(stationRows) >= len(queries):
                results.rows.extend(stationRows)
        else:
            results.rows.append(row)

    if not results.rows:
        if oneStopMode and len(stations):
            raise NoDataError("No one-stop stations found")
        raise NoDataError("No available items found")

    if oneStopMode and not singleMode:
        results.rows.sort(key=lambda result: result.item.name())
    results.rows.sort(key=lambda result: result.station.name())
    if cmdenv.sortByUnits:
        results.summary.sort = "units"
        results.rows.sort(key=lambda result: result.price)
        results.rows.sort(key=lambda result: result.units, reverse=True)
    else:
        if not oneStopMode:
            results.summary.sort = "Price"
            results.rows.sort(key=lambda result: result.units, reverse=True)
            results.rows.sort(key=lambda result: result.price)
        if nearSystem and not cmdenv.sortByPrice:
            results.summary.sort = "Ly"
            results.rows.sort(key=lambda result: result.dist)

    limit = cmdenv.limit or 0
    if limit > 0:
        results.rows = results.rows[:limit]

    return results
Example #5
0
def run(results, cmdenv, tdb):
    from commands.commandenv import ResultRow

    cmdenv = results.cmdenv
    tdb = cmdenv.tdb
    srcSystem = cmdenv.nearSystem

    results.summary = ResultRow()
    results.limit = cmdenv.limit

    fields = [
        "si.station_id",
        "JULIANDAY('NOW') - JULIANDAY(MAX(si.modified))",
        "stn.ls_from_star",
    ]

    joins = []
    wheres = []
    havings = []

    if cmdenv.minAge:
        wheres.append(
            "(JULIANDAY('NOW') - JULIANDAY(si.modified) >= {})".format(
                cmdenv.minAge))

    nearSys = cmdenv.nearSystem
    if nearSys:
        maxLy = cmdenv.maxLyPer or tdb.maxSystemLinkLy
        maxLy2 = maxLy**2
        fields.append("dist2("
                      "sys.pos_x, sys.pos_y, sys.pos_z,"
                      "{}, {}, {}"
                      ") AS d2".format(
                          nearSys.posX,
                          nearSys.posY,
                          nearSys.posZ,
                      ))
        joins.append("INNER JOIN System sys USING (system_id)")
        wheres.append("""(
                sys.pos_x BETWEEN {} and {}
                AND sys.pos_y BETWEEN {} and {}
                AND sys.pos_z BETWEEN {} and {}
        )""".format(
            nearSys.posX - maxLy,
            nearSys.posX + maxLy,
            nearSys.posY - maxLy,
            nearSys.posY + maxLy,
            nearSys.posZ - maxLy,
            nearSys.posZ + maxLy,
        ))
        havings.append("d2 <= {}".format(maxLy2))
    else:
        fields.append("0")

    fieldStr = ','.join(fields)

    if joins:
        joinStr = ' '.join(joins)
    else:
        joinStr = ''

    if wheres:
        whereStr = 'WHERE ' + ' AND '.join(wheres)
    else:
        whereStr = ''

    if havings:
        haveStr = 'HAVING ' + ' AND '.join(havings)
    else:
        haveStr = ''

    stmt = """
            SELECT  {fields}
              FROM  StationItem as si
                    INNER JOIN Station stn USING (station_id)
                    {joins}
             {wheres}
             GROUP  BY 1
             {having}
             ORDER  BY 2 DESC
    """.format(
        fields=fieldStr,
        joins=joinStr,
        wheres=whereStr,
        having=haveStr,
    )

    cmdenv.DEBUG1(stmt)

    for (stnID, age, ls, dist2) in tdb.query(stmt):
        cmdenv.DEBUG2("{}:{}:{}", stnID, age, ls)
        row = ResultRow()
        row.station = tdb.stationByID[stnID]
        row.age = age
        if ls:
            row.ls = "{:n}".format(ls)
        else:
            row.ls = "?"
        row.dist = dist2**0.5
        results.rows.append(row)

    if cmdenv.route and len(results.rows) > 1:

        def walk(startNode, dist):
            rows = results.rows
            startNode = rows[startNode]
            openList = set(rows)
            path = [startNode]
            openList.remove(startNode)
            while len(path) < len(rows):
                lastNode = path[-1]
                distFn = lastNode.station.system.distanceTo
                nearest = min(openList,
                              key=lambda row: distFn(row.station.system))
                openList.remove(nearest)
                path.append(nearest)
                dist += distFn(nearest.station.system)
            return (path, dist)

        if cmdenv.near:
            bestPath = walk(0, results.rows[0].dist)
        else:
            bestPath = (results.rows, float("inf"))
            for i in range(len(results.rows)):
                path = walk(i, 0)
                if path[1] < bestPath[1]:
                    bestPath = path
        results.rows[:] = bestPath[0]

    if cmdenv.limit:
        results.rows[:] = results.rows[:cmdenv.limit]

    return results
Example #6
0
def run(results, cmdenv, tdb):
    cmdenv = results.cmdenv
    tdb = cmdenv.tdb
    srcSystem = cmdenv.nearSystem

    ly = cmdenv.maxLyPer
    if ly is None:
        ly = tdb.maxSystemLinkLy

    results.summary = ResultRow()
    results.summary.near = srcSystem
    results.summary.ly = ly
    results.summary.stations = 0

    distances = {srcSystem: 0.0}

    # Calculate the bounding dimensions
    for destSys, dist in tdb.genSystemsInRange(srcSystem, ly):
        distances[destSys] = dist

    showStations = cmdenv.detail
    wantStations = cmdenv.stations
    padSize = cmdenv.padSize
    planetary = cmdenv.planetary
    wantNoPlanet = cmdenv.noPlanet
    wantTrading = cmdenv.trading
    wantShipYard = cmdenv.shipyard
    wantBlackMarket = cmdenv.blackMarket
    wantOutfitting = cmdenv.outfitting
    wantRearm = cmdenv.rearm
    wantRefuel = cmdenv.refuel
    wantRepair = cmdenv.repair

    def station_filter(stations):
        for station in stations:
            if wantNoPlanet and station.planetary != 'N':
                continue
            if wantTrading and not station.isTrading:
                continue
            if wantBlackMarket and station.blackMarket != 'Y':
                continue
            if wantShipYard and station.shipyard != 'Y':
                continue
            if padSize and not station.checkPadSize(padSize):
                continue
            if planetary and not station.checkPlanetary(planetary):
                continue
            if wantOutfitting and station.outfitting != 'Y':
                continue
            if wantRearm and station.rearm != 'Y':
                continue
            if wantRefuel and station.refuel != 'Y':
                continue
            if wantRepair and station.repair != 'Y':
                continue
            yield station

    for (system, dist) in sorted(distances.items(), key=lambda x: x[1]):
        if showStations or wantStations:
            stations = []
            for (station) in station_filter(system.stations):
                stations.append(
                    ResultRow(
                        station=station,
                        age=station.itemDataAgeStr,
                    ))
            if not stations and wantStations:
                continue

        row = ResultRow()
        row.system = system
        row.dist = dist
        row.stations = stations if showStations else []
        results.rows.append(row)
        results.summary.stations += len(row.stations)

    return results
def run(results, cmdenv, tdb):
    from commands.commandenv import ResultRow

    if cmdenv.lt and cmdenv.gt:
        if cmdenv.lt <= cmdenv.gt:
            raise CommandLineError("--gt must be lower than --lt")

    item = tdb.lookupItem(cmdenv.item)
    cmdenv.DEBUG0("Looking up item {} (#{})", item.name(), item.ID)

    avoidSystems = {s for s in cmdenv.avoidPlaces if isinstance(s, System)}
    avoidStations = {s for s in cmdenv.avoidPlaces if isinstance(s, Station)}

    results.summary = ResultRow()
    results.summary.item = item
    results.summary.avoidSystems = avoidSystems
    results.summary.avoidStations = avoidStations

    if cmdenv.detail:
        avgPrice = tdb.query("""
            SELECT AVG(si.demand_price)
              FROM StationItem AS si
             WHERE si.item_id = ? AND si.demand_price > 0
        """, [item.ID]).fetchone()[0]
        results.summary.avg = int(avgPrice)

    # Constraints
    tables = "StationItem AS si"
    constraints = [
        "(item_id = {} AND demand_price > 0)".format(item.ID),
    ]
    columns = [
        'si.station_id',
        'si.demand_price',
        'si.demand_units',
    ]
    bindValues = []

    if cmdenv.demand:
        constraints.append("(demand_units >= ?)")
        bindValues.append(cmdenv.demand)

    if cmdenv.lt:
        constraints.append("(demand_price < ?)")
        bindValues.append(cmdenv.lt)
    if cmdenv.gt:
        constraints.append("(demand_price > ?)")
        bindValues.append(cmdenv.gt)

    nearSystem = cmdenv.nearSystem
    if nearSystem:
        maxLy = cmdenv.maxLyPer or tdb.maxSystemLinkLy
        results.summary.near = nearSystem
        results.summary.ly = maxLy
        distanceFn = nearSystem.distanceTo
    else:
        distanceFn = None

    whereClause = ' AND '.join(constraints)
    stmt = """SELECT DISTINCT {columns} FROM {tables} WHERE {where}""".format(
        columns=','.join(columns),
        tables=tables,
        where=whereClause
    )
    cmdenv.DEBUG0('SQL: {}', stmt)
    cur = tdb.query(stmt, bindValues)

    stationByID = tdb.stationByID
    padSize = cmdenv.padSize
    wantBlackMarket = cmdenv.blackMarket

    for (stationID, priceCr, demand) in cur:
        station = stationByID[stationID]
        if padSize and not station.checkPadSize(padSize):
            continue
        if wantBlackMarket and station.blackMarket != 'Y':
            continue
        if station in avoidStations:
            continue
        if station.system in avoidSystems:
            continue

        row = ResultRow()
        row.station = station
        if distanceFn:
            distance = distanceFn(row.station.system)
            if distance > maxLy:
                continue
            row.dist = distance
        row.price = priceCr
        row.demand = demand
        row.age = station.itemDataAgeStr
        results.rows.append(row)

    if not results.rows:
        raise NoDataError("No available items found")

    results.summary.sort = "Price"
    results.rows.sort(key=lambda result: result.demand, reverse=True)
    results.rows.sort(key=lambda result: result.price, reverse=True)
    if nearSystem and not cmdenv.sortByPrice:
        results.summary.sort = "Dist"
        results.rows.sort(key=lambda result: result.dist)

    limit = cmdenv.limit or 0
    if limit > 0:
        results.rows = results.rows[:limit]

    return results
Example #8
0
def run(results, cmdenv, tdb):
    if cmdenv.lt and cmdenv.gt:
        if cmdenv.lt <= cmdenv.gt:
            raise CommandLineError("--gt must be lower than --lt")

    # Find out what we're looking for.
    queries, mode = get_lookup_list(cmdenv, tdb)
    cmdenv.DEBUG0("{} query: {}", mode, queries.values())

    avoidSystems = {s for s in cmdenv.avoidPlaces if isinstance(s, System)}
    avoidStations = {s for s in cmdenv.avoidPlaces if isinstance(s, Station)}

    # Summarize
    results.summary = ResultRow()
    results.summary.mode = mode
    results.summary.queries = queries
    results.summary.oneStop = cmdenv.oneStop
    results.summary.avoidSystems = avoidSystems
    results.summary.avoidStations = avoidStations

    # In single mode with detail enabled, add average reports.
    # Thus if you're looking up "algae" or the "asp", it'll
    # tell you the average/ship cost.
    singleMode = len(queries) == 1
    if singleMode and cmdenv.detail:
        first = list(queries.values())[0]
        if mode is SHIP_MODE:
            results.summary.avg = first.cost
        else:
            avgPrice = tdb.query("""
                SELECT AVG(si.supply_price)
                  FROM StationItem AS si
                 WHERE si.item_id = ? AND si.supply_price > 0
            """, [first.ID]).fetchone()[0]
            results.summary.avg = int(avgPrice)

    # System-based search
    nearSystem = cmdenv.nearSystem
    if nearSystem:
        maxLy = cmdenv.maxLyPer or tdb.maxSystemLinkLy
        results.summary.near = nearSystem
        results.summary.ly = maxLy
        distanceFn = nearSystem.distanceTo
    else:
        distanceFn = None

    oneStopMode = cmdenv.oneStop
    padSize = cmdenv.padSize
    wantBlackMarket = cmdenv.blackMarket

    stations = defaultdict(list)
    stationByID = tdb.stationByID

    cur = sql_query(cmdenv, tdb, queries, mode)
    for (ID, stationID, price, units) in cur:
        station = stationByID[stationID]
        if padSize and not station.checkPadSize(padSize):
            continue
        if wantBlackMarket and station.blackMarket != 'Y':
            continue
        if station in avoidStations:
            continue
        if station.system in avoidSystems:
            continue

        row = ResultRow()
        row.station = station
        if distanceFn:
            distance = distanceFn(row.station.system)
            if distance > maxLy:
                continue
            row.dist = distance
        row.item = queries[ID]
        row.price = price
        row.units = units
        row.age = station.itemDataAgeStr
        if oneStopMode:
            stationRows = stations[stationID]
            stationRows.append(row)
            if len(stationRows) >= len(queries):
                results.rows.extend(stationRows)
        else:
            results.rows.append(row)

    if not results.rows:
        if oneStopMode and len(stations):
            raise NoDataError("No one-stop stations found")
        raise NoDataError("No available items found")

    if oneStopMode and not singleMode:
        results.rows.sort(key=lambda result: result.item.name())
    results.rows.sort(key=lambda result: result.station.name())
    if cmdenv.sortByUnits:
        results.summary.sort = "units"
        results.rows.sort(key=lambda result: result.price)
        results.rows.sort(key=lambda result: result.units, reverse=True)
    else:
        if not oneStopMode:
            results.summary.sort = "Price"
            results.rows.sort(key=lambda result: result.units, reverse=True)
            results.rows.sort(key=lambda result: result.price)
        if nearSystem and not cmdenv.sortByPrice:
            results.summary.sort = "Ly"
            results.rows.sort(key=lambda result: result.dist)

    limit = cmdenv.limit or 0
    if limit > 0:
        results.rows = results.rows[:limit]

    return results
def run(results, cmdenv, tdb):
    from commands.commandenv import ResultRow

    cmdenv = results.cmdenv
    tdb = cmdenv.tdb
    srcSystem = cmdenv.nearSystem

    results.summary = ResultRow()
    results.limit = cmdenv.limit

    fields = [
        "si.station_id",
        "JULIANDAY('NOW') - JULIANDAY(MAX(si.modified))",
        "stn.ls_from_star",
    ]

    joins = []
    wheres = []
    havings = []

    if cmdenv.minAge:
        wheres.append(
            "(JULIANDAY('NOW') - JULIANDAY(si.modified) >= {})"
            .format(cmdenv.minAge)
        )

    nearSys = cmdenv.nearSystem
    if nearSys:
        maxLy = cmdenv.maxLyPer or tdb.maxSystemLinkLy
        maxLy2 = maxLy ** 2
        fields.append(
                "dist2("
                    "sys.pos_x, sys.pos_y, sys.pos_z,"
                    "{}, {}, {}"
                ") AS d2".format(
                    nearSys.posX,
                    nearSys.posY,
                    nearSys.posZ,
        ))
        joins.append("INNER JOIN System sys USING (system_id)")
        wheres.append("""(
                sys.pos_x BETWEEN {} and {}
                AND sys.pos_y BETWEEN {} and {}
                AND sys.pos_z BETWEEN {} and {}
        )""".format(
                nearSys.posX - maxLy,
                nearSys.posX + maxLy,
                nearSys.posY - maxLy,
                nearSys.posY + maxLy,
                nearSys.posZ - maxLy,
                nearSys.posZ + maxLy,
        ))
        havings.append("d2 <= {}".format(maxLy2))
    else:
        fields.append("0")

    fieldStr = ','.join(fields)

    if joins:
        joinStr = ' '.join(joins)
    else:
        joinStr = ''

    if wheres:
        whereStr = 'WHERE ' + ' AND '.join(wheres)
    else:
        whereStr = ''

    if havings:
        haveStr = 'HAVING ' + ' AND '.join(havings)
    else:
        haveStr = ''

    stmt = """
            SELECT  {fields}
              FROM  StationItem as si
                    INNER JOIN Station stn USING (station_id)
                    {joins}
             {wheres}
             GROUP  BY 1
             {having}
             ORDER  BY 2 DESC
    """.format(
            fields=fieldStr,
            joins=joinStr,
            wheres=whereStr,
            having=haveStr,
    )

    cmdenv.DEBUG1(stmt)

    for (stnID, age, ls, dist2) in tdb.query(stmt):
        cmdenv.DEBUG2("{}:{}:{}", stnID, age, ls)
        row = ResultRow()
        row.station = tdb.stationByID[stnID]
        row.age = age
        if ls:
            row.ls = "{:n}".format(ls)
        else:
            row.ls = "?"
        row.dist = dist2 ** 0.5
        results.rows.append(row)

    if cmdenv.route and len(results.rows) > 1:
        def walk(startNode, dist):
            rows = results.rows
            startNode = rows[startNode]
            openList = set(rows)
            path = [startNode]
            openList.remove(startNode)
            while len(path) < len(rows):
                lastNode = path[-1]
                distFn = lastNode.station.system.distanceTo
                nearest = min(openList, key=lambda row: distFn(row.station.system))
                openList.remove(nearest)
                path.append(nearest)
                dist += distFn(nearest.station.system)
            return path, dist
        if cmdenv.near:
            bestPath = walk(0, results.rows[0].dist)
        else:
            bestPath = (results.rows, float("inf"))
            for i in range(len(results.rows)):
                path = walk(i, 0)
                if path[1] < bestPath[1]:
                    bestPath = path
        results.rows[:] = bestPath[0]

    if cmdenv.limit:
        results.rows[:] = results.rows[:cmdenv.limit]

    return results
def run(results, cmdenv, tdb):
    cmdenv = results.cmdenv
    tdb = cmdenv.tdb
    srcSystem = cmdenv.nearSystem

    ly = cmdenv.maxLyPer
    if ly is None:
        ly = tdb.maxSystemLinkLy

    results.summary = ResultRow()
    results.summary.near = srcSystem
    results.summary.ly = ly
    results.summary.stations = 0

    distances = { srcSystem: 0.0 }

    # Calculate the bounding dimensions
    for destSys, dist in tdb.genSystemsInRange(srcSystem, ly):
        distances[destSys] = dist

    showStations = cmdenv.detail
    wantStations = cmdenv.stations
    padSize = cmdenv.padSize
    wantTrading = cmdenv.trading
    wantShipYard = cmdenv.shipyard
    wantBlackMarket = cmdenv.blackMarket
    wantOutfitting = cmdenv.outfitting
    wantRearm = cmdenv.rearm
    wantRefuel = cmdenv.refuel
    wantRepair = cmdenv.repair

    def station_filter(stations):
        for station in stations:
            if wantTrading and not station.isTrading:
                continue
            if wantBlackMarket and station.blackMarket != 'Y':
                continue
            if wantShipYard and station.shipyard != 'Y':
                continue
            if padSize and not station.checkPadSize(padSize):
                continue
            if wantOutfitting and station.outfitting != 'Y':
                continue
            if wantRearm and station.rearm != 'Y':
                continue
            if wantRefuel and station.refuel != 'Y':
                continue
            if wantRepair and station.repair != 'Y':
                continue
            yield station

    for (system, dist) in sorted(distances.items(), key=lambda x: x[1]):
        if showStations or wantStations:
            stations = []
            for (station) in station_filter(system.stations):
                stations.append(
                    ResultRow(
                        station=station,
                        age=station.itemDataAgeStr,
                    )
                )
            if not stations and wantStations:
                continue

        row = ResultRow()
        row.system = system
        row.dist = dist
        row.stations = stations if showStations else []
        results.rows.append(row)
        results.summary.stations += len(row.stations)

    return results