koleo-cli 0.2.137__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.

Potentially problematic release.


This version of koleo-cli might be problematic. Click here for more details.

koleo/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .api import KoleoAPI
2
+
3
+
4
+ __all__ = ["KoleoAPI"]
koleo/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ from .cli import main
2
+
3
+
4
+ if __name__ == "__main__":
5
+ main()
koleo/api.py ADDED
@@ -0,0 +1,150 @@
1
+ import typing as t
2
+ from datetime import datetime
3
+
4
+ from requests import Session, PreparedRequest, Response
5
+
6
+ from koleo.types import *
7
+
8
+
9
+ class KoleoAPIException(Exception):
10
+ status: int
11
+ request: PreparedRequest
12
+ response: Response
13
+
14
+ def __init__(self, response: Response, *args: object) -> None:
15
+ super().__init__(*args)
16
+ self.status = response.status_code
17
+ self.request = response.request
18
+ self.response = response
19
+
20
+ @classmethod
21
+ def from_response(cls, response: Response) -> "KoleoAPIException":
22
+ if response.status_code == 404:
23
+ return KoleoNotFound(response)
24
+ elif response.status_code == 401:
25
+ return KoleoUnauthorized(response)
26
+ elif response.status_code == 403:
27
+ return KoleoForbidden(response)
28
+ elif response.status_code == 429:
29
+ return KoleoRatelimited(response)
30
+ else:
31
+ return KoleoAPIException(response)
32
+
33
+
34
+ class KoleoNotFound(KoleoAPIException):
35
+ pass
36
+
37
+
38
+ class KoleoForbidden(KoleoAPIException):
39
+ pass
40
+
41
+
42
+ class KoleoUnauthorized(KoleoAPIException):
43
+ pass
44
+
45
+
46
+ class KoleoRatelimited(KoleoAPIException):
47
+ pass
48
+
49
+
50
+ class KoleoAPI:
51
+ def __init__(self) -> None:
52
+ self.session = Session()
53
+ self.base_url = "https://koleo.pl"
54
+ self.version = 2
55
+ self.base_headers = {
56
+ "x-koleo-version": str(self.version),
57
+ "User-Agent": "Koleo-CLI(https://pypi.org/project/koleo-cli)",
58
+ }
59
+
60
+ def _get(self, path, *args, **kwargs) -> Response:
61
+ headers = {**self.base_headers, **kwargs.get("headers", {})}
62
+ r = self.session.get(self.base_url + path, *args, headers=headers, **kwargs)
63
+ if not r.ok:
64
+ raise KoleoAPIException.from_response(r)
65
+ return r
66
+
67
+ def _get_json(self, path, *args, **kwargs) -> t.Any:
68
+ r = self._get(path, *args, **kwargs)
69
+ return r.json()
70
+
71
+ def _get_bytes(self, path, *args, **kwargs) -> bytes:
72
+ r = self._get(path, *args, **kwargs)
73
+ return r.content
74
+
75
+ def find_station(self, query: str, language: str = "pl") -> list[ExtendedStationInfo]:
76
+ # https://koleo.pl/ls?q=tere&language=pl
77
+ return self._get_json("/ls", query={"q": query, "language": language})
78
+
79
+ def get_station_by_slug(self, slug: str) -> ExtendedBaseStationInfo:
80
+ # https://koleo.pl/api/v2/main/stations/by_slug/inowroclaw
81
+ return self._get_json(
82
+ f"/api/v2/main/stations/by_slug/{slug}",
83
+ )
84
+
85
+ def get_station_info_by_slug(self, slug: str) -> StationDetails:
86
+ # https://koleo.pl/api/v2/main/station_info/inowroclaw
87
+ return self._get_json(
88
+ f"/api/v2/main/station_info/{slug}",
89
+ )
90
+
91
+ def get_departures(self, station_id: int, date: datetime) -> list[TrainOnStationInfo]:
92
+ # https://koleo.pl/api/v2/main/timetables/18705/2024-03-25/departures
93
+ return self._get_json(
94
+ f"/api/v2/main/timetables/{station_id}/{date.strftime("%Y-%m-%d")}/departures",
95
+ )
96
+
97
+ def get_arrivals(self, station_id: int, date: datetime) -> list[TrainOnStationInfo]:
98
+ # https://koleo.pl/api/v2/main/timetables/18705/2024-03-25/arrivals
99
+ return self._get_json(
100
+ f"/api/v2/main/timetables/{station_id}/{date.strftime("%Y-%m-%d")}/arrivals",
101
+ )
102
+
103
+ def get_train_calendars(self, brand_name: str, number: int, name: str | None = None) -> TrainCalendarResponse:
104
+ # https://koleo.pl/pl/train_calendars?brand=REG&nr=10417
105
+ # https://koleo.pl/pl/train_calendars?brand=IC&nr=1106&name=ESPERANTO ; WHY!!!! WHY!!!!!!1
106
+ params = {"brand": brand_name, "nr": number}
107
+ if name:
108
+ params["name"] = name.upper() # WHY!!!!!!!!!
109
+ return self._get_json("/api/v2/main/train_calendars", params=params)
110
+
111
+ def get_train(self, id: int) -> TrainDetailResponse:
112
+ # https://koleo.pl/pl/trains/142821312
113
+ return self._get_json(f"/api/v2/main/trains/{id}")
114
+
115
+ def get_connections(
116
+ self,
117
+ start: str,
118
+ end: str,
119
+ brand_ids: list[int],
120
+ date: datetime,
121
+ direct: bool = False,
122
+ purchasable: bool = False
123
+ ) -> ...:
124
+ params = {
125
+ "query[date]": date.strftime("%d-%m-%Y %H:%M:%S"),
126
+ "query[start_station]": start,
127
+ "query[end_station]": end,
128
+ "query[only_purchasable]": purchasable,
129
+ "query[direct]": direct,
130
+ "query[brand_ids]": brand_ids
131
+ }
132
+ return self._get_json("/api/v2/main/connections", params=params)
133
+
134
+ def get_brands(self) -> list[ApiBrand]:
135
+ # https://koleo.pl/api/v2/main/brands
136
+ return self._get_json(
137
+ "/api/v2/main/brands",
138
+ )
139
+
140
+ def get_carriers(self) -> list[Carrier]:
141
+ # https://koleo.pl/api/v2/main/carriers
142
+ return self._get_json(
143
+ "/api/v2/main/carriers",
144
+ )
145
+
146
+ def get_discounts(self) -> list[DiscountInfo]:
147
+ # https://koleo.pl/api/v2/main/discounts
148
+ return self._get_json(
149
+ "/api/v2/main/discounts",
150
+ )
koleo/cli.py ADDED
@@ -0,0 +1,231 @@
1
+ from datetime import datetime
2
+ from argparse import ArgumentParser
3
+
4
+ from rich.console import Console
5
+ from rich.traceback import install
6
+
7
+ from .api import KoleoAPI
8
+ from .types import TrainOnStationInfo, TrainDetailResponse
9
+ from .utils import name_to_slug, parse_datetime, time_dict_to_dt
10
+ from .storage import Storage, DEFAULT_CONFIG_PATH
11
+
12
+ install(show_locals=True)
13
+
14
+
15
+ class CLI:
16
+ def __init__(
17
+ self,
18
+ no_color: bool = False,
19
+ client: KoleoAPI | None = None,
20
+ storage: Storage | None = None,
21
+ ) -> None:
22
+ self._client = client
23
+ self._storage = storage
24
+ self.console = Console(color_system="standard", no_color=no_color)
25
+
26
+ @property
27
+ def client(self) -> KoleoAPI:
28
+ if not self._client:
29
+ raise ValueError("Client not set!")
30
+ return self._client
31
+
32
+ @client.setter
33
+ def client(self, client: KoleoAPI):
34
+ self._client = client
35
+
36
+ @property
37
+ def storage(self) -> Storage:
38
+ if not self._storage:
39
+ raise ValueError("Storage not set!")
40
+ return self._storage
41
+
42
+ @storage.setter
43
+ def storage(self, storage: Storage):
44
+ self._storage = storage
45
+
46
+ def list_stations(self, name: str): ...
47
+
48
+ def get_departures(self, station_id: int, date: datetime):
49
+ trains = self.client.get_departures(station_id, date)
50
+ trains = [
51
+ i
52
+ for i in trains
53
+ if datetime.fromisoformat(i["departure"]).timestamp() > date.timestamp() # type: ignore
54
+ ]
55
+ table = self.trains_on_station_table(trains)
56
+ self.console.print(table)
57
+ return table
58
+
59
+ def get_arrivals(self, station_id: int, date: datetime):
60
+ trains = self.client.get_arrivals(station_id, date)
61
+ trains = [
62
+ i
63
+ for i in trains
64
+ if datetime.fromisoformat(i["arrival"]).timestamp() > date.timestamp() # type: ignore
65
+ ]
66
+ table = self.trains_on_station_table(trains, type=2)
67
+ self.console.print(table)
68
+ return table
69
+
70
+ def full_departures(self, station: str, date: datetime):
71
+ slug = name_to_slug(station)
72
+ st = self.storage.get_cache(f"st-{slug}") or self.storage.set_cache(
73
+ f"st-{slug}", self.client.get_station_by_slug(slug)
74
+ )
75
+ station_info = f"[bold blue]{st["name"]}[/bold blue] ID: {st["id"]}"
76
+ self.console.print(station_info)
77
+ self.get_departures(st["id"], date)
78
+
79
+ def full_arrivals(self, station: str, date: datetime):
80
+ slug = name_to_slug(station)
81
+ st = self.storage.get_cache(f"st-{slug}") or self.storage.set_cache(
82
+ f"st-{slug}", self.client.get_station_by_slug(slug)
83
+ )
84
+ station_info = f"[bold blue]{st["name"]}[/bold blue] ID: {st["id"]}"
85
+ self.console.print(station_info)
86
+ self.get_arrivals(st["id"], date)
87
+
88
+ def train_info(self, brand: str, name: str, date: datetime):
89
+ brand = brand.upper().strip()
90
+ name = name.upper().strip()
91
+ cache_id = f"tc-{brand}-{name}"
92
+ if name.isnumeric():
93
+ number = int(name)
94
+ name = ""
95
+ elif len(parts := name.split(" ")) == 2 or len(parts := name.split("-")) == 2:
96
+ number, name = parts
97
+ number = int(number)
98
+ else:
99
+ raise ValueError("Invalid train name!")
100
+ train_calendars = self.storage.get_cache(cache_id) or self.storage.set_cache(
101
+ cache_id, self.client.get_train_calendars(brand, number, name)
102
+ )
103
+ brands = self.storage.get_cache("brands") or self.storage.set_cache("brands", self.client.get_brands())
104
+ train_id = train_calendars["train_calendars"][0]["date_train_map"][date.strftime("%Y-%m-%d")]
105
+ train_details = self.client.get_train(train_id)
106
+ brand = next(iter(i for i in brands if i["id"] == train_details["train"]["brand_id"]), {}).get("name", "")
107
+ data = f"{brand} {train_details["train"]["train_full_name"]}\n"
108
+ vehicle_types: dict[str, str] = {
109
+ stop["station_display_name"]: stop["vehicle_type"]
110
+ for stop in train_details["stops"]
111
+ if stop["vehicle_type"]
112
+ }
113
+ if vehicle_types:
114
+ keys = list(vehicle_types.keys())
115
+ start = keys[0]
116
+ for i in range(1, len(keys)):
117
+ if vehicle_types[keys[i]] != vehicle_types[start]:
118
+ data += f"[bold green] {start} - {keys[i]}:[/bold green] {vehicle_types[start]}\n"
119
+ start = keys[i]
120
+ data += f"[bold green] {start} - {keys[-1]}:[/bold green] {vehicle_types[start]}"
121
+ self.console.print(data)
122
+ self.console.print(self.train_route_table(train_details))
123
+
124
+ def route(self, start: str, end: str, date: datetime, direct: bool = False, purchasable: bool = False):
125
+ slug = name_to_slug(station)
126
+ st = self.storage.get_cache(f"st-{slug}") or self.storage.set_cache(
127
+ f"st-{slug}", self.client.get_station_by_slug(slug)
128
+ )
129
+ station_info = f"[bold blue]{st["name"]}[/bold blue] ID: {st["id"]}"
130
+ self.console.print(station_info)
131
+ self.get_arrivals(st["id"], date)
132
+
133
+ def trains_on_station_table(self, trains: list[TrainOnStationInfo], type: int = 1):
134
+ parts = []
135
+ brands = self.storage.get_cache("brands") or self.storage.set_cache("brands", self.client.get_brands())
136
+ for train in trains:
137
+ time = train["departure"] if type == 1 else train["arrival"]
138
+ assert time
139
+ brand = next(iter(i for i in brands if i["id"] == train["brand_id"]), {}).get("name")
140
+ parts.append(
141
+ f"[bold green]{time[11:16]}[/bold green] {brand} {train["train_full_name"]}[purple] {train["stations"][0]["name"]} [/purple]"
142
+ )
143
+ return "\n".join(parts)
144
+
145
+ def train_route_table(self, train: TrainDetailResponse):
146
+ parts = []
147
+ for stop in train["stops"]:
148
+ arr = time_dict_to_dt(stop["arrival"])
149
+ dep = time_dict_to_dt(stop["departure"])
150
+ parts.append(
151
+ f"[white underline]{stop["distance"] / 1000:0.4}km[/white underline] [bold green]{arr.strftime("%H:%M")}[/bold green] - [bold red]{dep.strftime("%H:%M")}[/bold red] [purple] {stop["station_display_name"]} {stop["platform"]} [/purple]"
152
+ )
153
+ return "\n".join(parts)
154
+
155
+
156
+ def main():
157
+ cli = CLI()
158
+
159
+ parser = ArgumentParser("koleo", description="Koleo CLI")
160
+ parser.add_argument("-c", "--config", help="Custom config path.", default=DEFAULT_CONFIG_PATH)
161
+ parser.add_argument("--nocolor", help="Disable color output", action="store_true", default=False)
162
+ subparsers = parser.add_subparsers(title="actions", required=False)
163
+
164
+ departures = subparsers.add_parser("departures", aliases=["d", "dep"], help="Allows you to list station departures")
165
+ departures.add_argument(
166
+ "station",
167
+ help="The station name",
168
+ default=None,
169
+ nargs="?",
170
+ )
171
+ departures.add_argument(
172
+ "-d",
173
+ "--date",
174
+ help="the departure date",
175
+ type=lambda s: parse_datetime(s),
176
+ default=datetime.now(),
177
+ )
178
+ departures.add_argument("-s", "--save", help="save the station as your default one", action="store_true")
179
+ departures.set_defaults(func=cli.full_departures, pass_=["station", "date"])
180
+
181
+ arrivals = subparsers.add_parser("arrivals", aliases=["a", "arr"], help="Allows you to list station departures")
182
+ arrivals.add_argument(
183
+ "station",
184
+ help="The station name",
185
+ default=None,
186
+ nargs="?",
187
+ )
188
+ arrivals.add_argument(
189
+ "-d",
190
+ "--date",
191
+ help="the arrival date",
192
+ type=lambda s: parse_datetime(s),
193
+ default=datetime.now(),
194
+ )
195
+ arrivals.add_argument("-s", "--save", help="save the station as your default one", action="store_true")
196
+ arrivals.set_defaults(func=cli.full_arrivals, pass_=["station", "date"])
197
+
198
+ train_route = subparsers.add_parser(
199
+ "trainroute",
200
+ aliases=["r", "tr", "t"],
201
+ help="Allows you to show the train's route",
202
+ )
203
+ train_route.add_argument("brand", help="The brand name", type=str)
204
+ train_route.add_argument("name", help="The train name", type=str)
205
+ train_route.add_argument(
206
+ "-d",
207
+ "--date",
208
+ help="the date",
209
+ type=lambda s: parse_datetime(s),
210
+ default=datetime.now(),
211
+ )
212
+ train_route.set_defaults(func=cli.train_info, pass_=["brand", "name", "date"])
213
+
214
+ args = parser.parse_args()
215
+
216
+ storage = Storage.load(path=args.config)
217
+ client = KoleoAPI()
218
+ cli.client, cli.storage = client, storage
219
+ cli.console.no_color = args.nocolor
220
+ if hasattr(args, "station") and args.station is None:
221
+ args.station = storage.favourite_station
222
+ elif hasattr(args, "station") and args.save:
223
+ storage.favourite_station = args.station
224
+ storage.save()
225
+ if not hasattr(args, "func"):
226
+ if storage.favourite_station:
227
+ cli.full_departures(storage.favourite_station, datetime.now())
228
+ else:
229
+ raise ValueError("favourite station not set!")
230
+ else:
231
+ args.func(**{k: v for k, v in args.__dict__.items() if k in getattr(args, "pass_", [])})
koleo/storage.py ADDED
@@ -0,0 +1,71 @@
1
+ import typing as t
2
+ from time import time
3
+ from dataclasses import dataclass, asdict, field
4
+ from sys import platform
5
+ from json import dump, load
6
+ from os import makedirs, path as ospath
7
+
8
+
9
+ def get_adequate_config_path():
10
+ if platform == "darwin":
11
+ # i dont fucking know nor want to
12
+ return "~/Library/Preferences/koleo-cli/data.json"
13
+ elif "win" in platform:
14
+ # same with this
15
+ return "%USERPROFILE%\\AppData\\Local\\koleo-cli\\data.json"
16
+ else:
17
+ return "~/.config/koleo-cli.json"
18
+
19
+
20
+ DEFAULT_CONFIG_PATH = get_adequate_config_path()
21
+
22
+
23
+ T = t.TypeVar("T")
24
+
25
+
26
+ @dataclass
27
+ class Storage:
28
+ favourite_station: str | None = None
29
+ last_searched_connections: list[tuple[str, str]] = field(default_factory=list)
30
+ last_searched_stations: list[str] = field(default_factory=list)
31
+ cache: dict[str, tuple[int, t.Any]] = field(default_factory=dict)
32
+
33
+ def __post_init__(self):
34
+ self._path: str
35
+
36
+ @classmethod
37
+ def load(cls, *, path: str = DEFAULT_CONFIG_PATH) -> t.Self:
38
+ expanded = ospath.expanduser(path)
39
+ if ospath.exists(expanded):
40
+ with open(expanded, "r") as f:
41
+ data = load(f)
42
+ else:
43
+ data = {}
44
+ storage = cls(**data)
45
+ storage._path = path
46
+ return storage
47
+
48
+ def get_cache(self, id: str) -> t.Any | None:
49
+ cache_result = self.cache.get(id)
50
+ if not cache_result:
51
+ return
52
+ expiry, item = cache_result
53
+ if expiry > time():
54
+ return item
55
+ else:
56
+ self.cache.pop(id)
57
+ self.save()
58
+
59
+ def set_cache(self, id: str, item: T, ttl: int = 86400) -> T:
60
+ self.cache[id] = (int(time() + ttl), item)
61
+ self.save()
62
+ return item
63
+
64
+ def save(self):
65
+ expanded = ospath.expanduser(self._path)
66
+ dir = ospath.dirname(expanded)
67
+ if dir:
68
+ if not ospath.exists(dir):
69
+ makedirs(dir)
70
+ with open(expanded, "w+") as f:
71
+ dump(asdict(self), f)
koleo/types.py ADDED
@@ -0,0 +1,177 @@
1
+ import typing as t
2
+
3
+
4
+ class BaseStationInfo(t.TypedDict):
5
+ id: int
6
+ name: str
7
+ name_slug: str
8
+
9
+
10
+ # i want to kms
11
+ class ExtendedBaseStationInfo(BaseStationInfo):
12
+ hits: int
13
+ version: str # "A", "B"
14
+ is_group: bool
15
+ region: str
16
+ country: str
17
+ latitude: float
18
+ longitued: float
19
+
20
+
21
+ class ExtendedStationInfo(BaseStationInfo):
22
+ ibnr: int
23
+ localised_name: str
24
+ on_demand: bool
25
+
26
+
27
+ class StationBannerClockHandsPositon(t.TypedDict):
28
+ x: float
29
+ y: float
30
+
31
+
32
+ class StationBannerClockHands(t.TypedDict):
33
+ url: str | None
34
+ position: StationBannerClockHandsPositon
35
+
36
+
37
+ class StationBannerClock(t.TypedDict):
38
+ visible: bool | None
39
+ hands: StationBannerClockHands
40
+
41
+
42
+ class StationBanner(t.TypedDict):
43
+ url: str
44
+ clock: StationBannerClock
45
+
46
+
47
+ class Address(t.TypedDict):
48
+ full: str
49
+ zip: str
50
+
51
+
52
+ class Feature(t.TypedDict):
53
+ id: str
54
+ name: str
55
+ available: bool
56
+
57
+
58
+ class StationOpeningHours(t.TypedDict):
59
+ day: int # 0-6
60
+ open: str # 00:00
61
+ close: str # 24:00
62
+
63
+
64
+ class StationDetails(t.TypedDict):
65
+ banner: StationBanner
66
+ address: Address
67
+ lat: float
68
+ lon: float
69
+ opening_hours: list[StationOpeningHours]
70
+ features: list[Feature]
71
+
72
+
73
+ class StationLocalizedToTrain(BaseStationInfo):
74
+ train_id: int
75
+
76
+
77
+ class ApiBrand(t.TypedDict):
78
+ id: int
79
+ name: str # EIC, IC, IR, REG, ...
80
+ display_name: str
81
+ logo_text: str
82
+ color: str # hex
83
+ carrier_id: int
84
+
85
+
86
+ class Carrier(t.TypedDict):
87
+ id: int
88
+ name: str # Koleje Dolnośląskie, POLREGIO
89
+ short_name: str # KD, PR
90
+ slug: str
91
+ legal_name: str # PKP Szybka Kolej Miejska w Trójmieście Sp.z o.o.
92
+
93
+
94
+ class DiscountInfo(t.TypedDict):
95
+ id: int
96
+ passenger_percentage: int
97
+ display_passenger_percentage: float
98
+ flyer_second_class_percentage: int
99
+ flyer_first_class_percentage: int
100
+ express_second_class_percentage: int
101
+ express_first_class_percentage: int
102
+ dependent_on_ids: list[int]
103
+ name: str
104
+ rank: int
105
+ season_passenger_percentage: int
106
+ displayable: bool
107
+ is_company: bool
108
+
109
+
110
+ class TrainOnStationInfo(t.TypedDict):
111
+ arrival: str | None # first station
112
+ departure: str | None # last station
113
+ stations: list[StationLocalizedToTrain]
114
+ train_full_name: str
115
+ brand_id: int
116
+ platform: str # could be empty; some countries dont use arabic numerals or even tracks
117
+ track: str # could be empty; some countries dont use arabic numerals or even tracks
118
+
119
+
120
+ class TrainCalendar(t.TypedDict):
121
+ id: int
122
+ train_nr: int
123
+ train_name: str
124
+ trainBrand: int
125
+ dates: list[str] # Y-M-D
126
+ train_ids: list[int]
127
+ date_train_map: dict[str, int]
128
+
129
+
130
+ class TrainCalendarResponse(t.TypedDict):
131
+ train_calendars: list[TrainCalendar]
132
+
133
+
134
+ class TimeDict(t.TypedDict):
135
+ hour: int
136
+ minute: int
137
+ second: int
138
+
139
+
140
+ TrainAttribute = t.Tuple[int, str, str, str, bool, str]
141
+
142
+
143
+ class TrainDetail(t.TypedDict):
144
+ id: int
145
+ train_nr: int
146
+ name: str | None
147
+ train_full_name: str
148
+ run_desc: str # "09.09-15.09 - w pt - nd; 16.09-29.09, 14.10-03.11 - codziennie; 30.09-06.10 - w pn; 07.10-13.10 - we wt - nd; 04.11-10.11 - w pn - sb"
149
+ carrier_id: int
150
+ brand_id: int
151
+ train_name: int # wtf
152
+ duration_offset: int # wtf?
153
+ db_train_nr: int # lol
154
+
155
+
156
+ class TrainStop(t.TypedDict):
157
+ id: int
158
+ station_id: int
159
+ station_name: str
160
+ station_slug: str
161
+ train_id: int
162
+ arrival: TimeDict
163
+ departure: TimeDict
164
+ position: int # the stop nr
165
+ train_nr: int | None
166
+ brand_id: int
167
+ distance: int # meters
168
+ entry_only: bool
169
+ exit_only: bool
170
+ station_display_name: str
171
+ platform: str
172
+ vehicle_type: str | None # ED161
173
+
174
+
175
+ class TrainDetailResponse(t.TypedDict):
176
+ train: TrainDetail
177
+ stops: list[TrainStop]
koleo/utils.py ADDED
@@ -0,0 +1,41 @@
1
+ from datetime import datetime, time
2
+
3
+ from .types import TimeDict
4
+
5
+
6
+ def parse_datetime(s: str):
7
+ now = datetime.today()
8
+ try:
9
+ dt = datetime.strptime(s, "%d-%m")
10
+ return dt.replace(year=now.year, hour=0, minute=0)
11
+ except ValueError:
12
+ pass
13
+ try:
14
+ return datetime.strptime(s, "%Y-%m-%d").replace(hour=0, minute=0)
15
+ except ValueError:
16
+ pass
17
+ return datetime.combine(now, datetime.strptime(s, "%H:%M").time())
18
+
19
+
20
+ def time_dict_to_dt(s: TimeDict):
21
+ now = datetime.today()
22
+ return datetime.combine(now, time(s["hour"], s["minute"], s["second"]))
23
+
24
+
25
+ TRANSLITERATIONS = {
26
+ "ł": "l",
27
+ "ń": "n",
28
+ "ą": "a",
29
+ "ę": "e",
30
+ "ś": "s",
31
+ "ć": "c",
32
+ "ó": "o",
33
+ "ź": "z",
34
+ "ż": "z",
35
+ " ": "-",
36
+ "_": "-",
37
+ }
38
+
39
+
40
+ def name_to_slug(name: str) -> str:
41
+ return "".join([TRANSLITERATIONS.get(char, char) for char in name.lower()])
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.1
2
+ Name: koleo-cli
3
+ Version: 0.2.137
4
+ Summary: Koleo CLI
5
+ Home-page: https://github.com/lzgirlcat/koleo-cli
6
+ Author: Zoey !
7
+ Maintainer-email: cb98uzhd@duck.com
8
+ License: GNU General Public License v3.0
9
+ Project-URL: Source (GitHub), https://github.com/lzgirlcat/koleo-cli
10
+ Project-URL: Issue Tracker, https://github.com/lzgirlcat/koleo-cli/issues
11
+ Keywords: koleo,timetable,trains,rail,poland
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Operating System :: OS Independent
14
+ Requires-Python: >=3.12
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: rich==13.7.1
17
+ Requires-Dist: requests==2.31.0
18
+
@@ -0,0 +1,12 @@
1
+ koleo/__init__.py,sha256=ChPpKyc4vrcgWj-Uk_ZlDw32s_7iFpE3f1FTAd6zxPY,51
2
+ koleo/__main__.py,sha256=wu5N2wk8mvBgyvr2ghmQf4prezAe0_i-p123VVreyYc,62
3
+ koleo/api.py,sha256=8aYn4SsavjOx9cPHLgSlwVmbZaZWJNYpIL3lZPuvdTk,5070
4
+ koleo/cli.py,sha256=fd1O_gO5EF-7BrrLe79j7fn7SEKr5yLgWu33UIjLghM,9230
5
+ koleo/storage.py,sha256=uCh6edwizAuw1z_Ti5AXvDan2pJAJBSobCVmYw096F8,2015
6
+ koleo/types.py,sha256=8mAsRdNh3jMJwAV5KnHVvDQAekhs0N70EVem4p_w18o,3760
7
+ koleo/utils.py,sha256=MYfeQkk9spT2lmlFVsHlpzgnAf0hqN-gKEbgYASqW6U,904
8
+ koleo_cli-0.2.137.dist-info/METADATA,sha256=-PLG8A9e3iD-az9tyrdJEitqThaZrBkOhiWHWuMSPXk,624
9
+ koleo_cli-0.2.137.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
10
+ koleo_cli-0.2.137.dist-info/entry_points.txt,sha256=LtCidkVDq8Zd7-fxpRbys1Xa9LTHMZwXVbdcQEscdes,41
11
+ koleo_cli-0.2.137.dist-info/top_level.txt,sha256=AlWdXotkRYzHpFfOBYi6xOXl1H0zq4-tqtZ2XivoWB4,6
12
+ koleo_cli-0.2.137.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (74.1.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ koleo = koleo.cli:main
@@ -0,0 +1 @@
1
+ koleo