1212from rich import traceback
1313
1414from camply import __application__ , __version__
15- from camply .config import SearchConfig
15+ from camply .config import EquipmentOptions , SearchConfig
1616from camply .config .logging_config import set_up_logging
1717from camply .containers import SearchWindow
18- from camply .providers import RecreationDotGov
18+ from camply .providers import (
19+ GOING_TO_CAMP ,
20+ RECREATION_DOT_GOV ,
21+ YELLOWSTONE ,
22+ GoingToCampProvider ,
23+ RecreationDotGov ,
24+ )
1925from camply .search import CAMPSITE_SEARCH_PROVIDER , SearchYellowstone
2026from camply .utils import configure_camply , log_camply , make_list , yaml_utils
27+ from camply .utils .logging_utils import log_sorted_response
2128
2229logging .Logger .camply = log_camply
2330logger = logging .getLogger (__name__ )
2431
25- DEFAULT_CAMPLY_PROVIDER : str = "RecreationDotGov"
32+ DEFAULT_CAMPLY_PROVIDER : str = RECREATION_DOT_GOV
2633
2734
2835@dataclass
@@ -39,8 +46,9 @@ class CamplyContext:
3946 "--provider" ,
4047 show_default = False ,
4148 default = None ,
42- help = "Camping Search Provider. Options available are 'Yellowstone' and "
43- "'RecreationDotGov'. Defaults to 'RecreationDotGov', not case-sensitive." ,
49+ help = "Camping Search Provider. Options available are 'Yellowstone', "
50+ "'RecreationDotGov', and 'GoingToCamp'. Defaults to 'RecreationDotGov'"
51+ ", not case-sensitive." ,
4452)
4553debug_option = click .option (
4654 "--debug/--no-debug" , default = None , help = "Enable extra debugging output"
@@ -62,12 +70,36 @@ def _set_up_debug(debug: Optional[bool] = None) -> None:
6270 traceback .install (show_locals = debug )
6371
6472
73+ def _preferred_provider (context : CamplyContext , command_provider : Optional [str ]) -> str :
74+ """
75+ Called to get the preferred subcommands provider.
76+
77+ It establishes rules for the "preferred" provider. That is, when multiple
78+ providers are elgigible to serve a command, the one that is most specific is
79+ chosen. Preference is in the following order:
80+
81+ 1. The provider explicitly provided to a camply subcommand
82+ 2. The provider associated with CamplyContext
83+ 3. The default provider
84+
85+ The preferred provider is returned
86+ """
87+ if command_provider :
88+ return command_provider .lower ()
89+ elif command_provider is None and context .provider :
90+ return context .provider .lower ()
91+ else :
92+ return DEFAULT_CAMPLY_PROVIDER .lower ()
93+
94+
6595@click .group ()
6696@click .version_option (version = __version__ , prog_name = __application__ )
67- @provider_argument
6897@debug_option
98+ @provider_argument
6999@click .pass_context
70- def camply_command_line (ctx : click .core .Context , provider : str , debug : bool ) -> None :
100+ def camply_command_line (
101+ ctx : click .core .Context , debug : bool , provider : Optional [str ]
102+ ) -> None :
71103 """
72104 Welcome to camply, the campsite finder.
73105
@@ -128,34 +160,91 @@ def configure(context: CamplyContext, debug: bool) -> None:
128160)
129161
130162
163+ @camply_command_line .command ()
164+ @rec_area_argument
165+ @provider_argument
166+ @click .pass_obj
167+ def equipment_types (
168+ context : CamplyContext ,
169+ rec_area : Optional [int ] = None ,
170+ provider : str = DEFAULT_CAMPLY_PROVIDER ,
171+ ) -> None :
172+ """
173+ Retrieve a list of equipment supported by the current provider/recreaton area
174+
175+ Equipment are camping equipment that can be used at a campsite. Different providers
176+ and recreation areas have different types of equipment for which reservations can be made.
177+ """
178+ provider = _preferred_provider (context , provider )
179+ if not rec_area and provider == GOING_TO_CAMP :
180+ logger .error (
181+ "This provider requires --rec-area to be specified when listing equipment types"
182+ )
183+ exit (1 )
184+
185+ if provider == GOING_TO_CAMP :
186+ GoingToCampProvider ().list_equipment_types (rec_area [0 ])
187+ else :
188+ log_sorted_response (response_array = EquipmentOptions .__all_accepted_equipment__ )
189+
190+ exit (0 )
191+
192+
131193@camply_command_line .command ()
132194@search_argument
133195@state_argument
134196@debug_option
197+ @provider_argument
135198@click .pass_obj
136199def recreation_areas (
137- context : CamplyContext , search : Optional [str ], state : Optional [str ], debug : bool
200+ context : CamplyContext ,
201+ search : Optional [str ],
202+ state : Optional [str ],
203+ debug : bool ,
204+ provider : str = DEFAULT_CAMPLY_PROVIDER ,
138205) -> None :
139206 """
140207 Search for Recreation Areas and list them
141208
142209 Search for Recreation Areas and their IDs. Recreation Areas are places like
143210 National Parks and National Forests that can contain one or many campgrounds.
144211 """
212+ provider = _preferred_provider (context , provider )
145213 if context .debug is None :
146214 context .debug = debug
147215 _set_up_debug (debug = context .debug )
148- if all ([search is None , state is None ]):
216+
217+ # Recreation dot gov and yellowstone require --state or --search, but going to
218+ # camp does not, since all of its "rec areas" are a very few.
219+ if all ([search is None , state is None , provider != GOING_TO_CAMP ]):
149220 logger .error (
150- "You must add a --search or --state parameter to search "
151- "for Recreation Areas."
221+ "You must add a --search, --state, or --provider parameter "
222+ "to search for Recreation Areas."
152223 )
153224 exit (1 )
154- camp_finder = RecreationDotGov ()
225+ if all ([search is None , state is not None , provider == GOING_TO_CAMP ]):
226+ logger .error (
227+ "GoingToCamp does not support filtering recreation areas by state. Leave --state blank."
228+ )
229+ exit (1 )
230+
231+ camp_provider = None
232+ if provider == RECREATION_DOT_GOV :
233+ camp_provider = RecreationDotGov ()
234+ elif provider == GOING_TO_CAMP :
235+ camp_provider = GoingToCampProvider ()
236+ else :
237+ logger .error (
238+ "The provider you specified does not exist or does notsupport"
239+ "listing recreation areas. See --help for available providers"
240+ )
241+ exit (1 )
242+
155243 params = dict ()
156244 if state is not None :
157245 params .update (dict (state = state ))
158- camp_finder .find_recreation_areas (search_string = search , ** params )
246+
247+ camp_provider .find_recreation_areas (search_string = search , ** params )
159248
160249
161250@camply_command_line .command ()
@@ -175,7 +264,7 @@ def campgrounds(
175264 rec_area : Optional [int ] = None ,
176265 campground : Optional [int ] = None ,
177266 campsite : Optional [int ] = None ,
178- provider : Optional [ str ] = "RecreationDotGov" ,
267+ provider : str = DEFAULT_CAMPLY_PROVIDER ,
179268) -> None :
180269 """
181270 Search for Campgrounds (inside of Recreation Areas) and list them
@@ -185,13 +274,12 @@ def campgrounds(
185274 multiple campsites, others are facilities like fire towers or cabins that might only
186275 contain a single 'campsite' to book.
187276 """
277+ provider = _preferred_provider (context , provider )
188278 if context .debug is None :
189279 context .debug = debug
190280 _set_up_debug (debug = context .debug )
191- if context .provider is None :
192- context .provider = provider
193- provider = DEFAULT_CAMPLY_PROVIDER if context .provider is None else context .provider
194- if provider .lower () == "yellowstone" :
281+
282+ if provider == YELLOWSTONE :
195283 SearchYellowstone .print_campgrounds ()
196284 exit (0 )
197285 if all (
@@ -208,6 +296,20 @@ def campgrounds(
208296 "or --rec-area parameter to search for Campgrounds."
209297 )
210298 exit (1 )
299+
300+ if provider == YELLOWSTONE :
301+ SearchYellowstone .print_campgrounds ()
302+ exit (0 )
303+ if provider == GOING_TO_CAMP :
304+ if len (rec_area ) == 0 :
305+ logger .error ("You must specify at least one --rec-area" )
306+ exit (1 )
307+ rec_area_id = int (rec_area [0 ])
308+ GoingToCampProvider ().find_facilities_per_recreation_area (
309+ rec_area_id = rec_area_id , campground_id = campground , search_string = search
310+ )
311+ exit (0 )
312+
211313 camp_finder = RecreationDotGov ()
212314 params = dict ()
213315 if state is not None :
@@ -310,6 +412,19 @@ def campgrounds(
310412 "equipment names include `Tent`, `RV`. `Trailer`, `Vehicle` and are "
311413 "not case-sensitive." ,
312414)
415+ equipment_id_argument = click .option (
416+ "--equipment-id" ,
417+ default = None ,
418+ help = """
419+ Search for campsites campaitble with specific equipment categories. Going To
420+ Camp uses equipment category IDs for filtering campsites by equipment. Every
421+ recreation area has equipment categories unique to it.
422+
423+ Use `camply equipment-types --provider goingtocamp --rec-area <rec area id>`
424+ to get a listing of equipment for an area.
425+ """ ,
426+ )
427+
313428offline_search_argument = click .option (
314429 "--offline-search" ,
315430 is_flag = True ,
@@ -359,7 +474,7 @@ def _validate_campsites(
359474 """
360475 Validate the campsites portion of the CLI
361476 """
362- if provider . lower () == "recreationdotgov" and all (
477+ if provider == RECREATION_DOT_GOV and all (
363478 [
364479 len (rec_area ) == 0 ,
365480 len (campground ) == 0 ,
@@ -401,9 +516,10 @@ def _validate_campsites(
401516@notifications_argument
402517@polling_interval_argument
403518@continuous_argument
404- @provider_argument
405519@equipment_argument
520+ @equipment_id_argument
406521@nights_argument
522+ @provider_argument
407523@weekends_argument
408524@end_date_argument
409525@start_date_argument
@@ -422,16 +538,17 @@ def campsites(
422538 end_date : Optional [str ] = None ,
423539 weekends : bool = False ,
424540 nights : int = 1 ,
425- provider : str = "RecreationDotGov" ,
541+ provider : str = DEFAULT_CAMPLY_PROVIDER ,
426542 continuous : bool = False ,
427543 polling_interval : int = SearchConfig .RECOMMENDED_POLLING_INTERVAL ,
428544 notifications : Union [str , List [str ]] = "silent" ,
429545 notify_first_try : bool = False ,
430546 search_forever : bool = False ,
431547 yaml_config : Optional [str ] = None ,
432- equipment : Optional [List [str ]] = None ,
433548 offline_search : bool = False ,
434549 offline_search_path : Optional [str ] = None ,
550+ equipment : Optional [Union [str , int ]] = None ,
551+ equipment_id : Optional [Union [str , int ]] = None ,
435552) -> None :
436553 """
437554 Find available Campsites using search criteria
@@ -443,12 +560,11 @@ def campsites(
443560 functionality can be enabled with `--continuous` and notifications can be enabled using
444561 `--notifications`.
445562 """
563+ provider = _preferred_provider (context , provider )
446564 if context .debug is None :
447565 context .debug = debug
448566 _set_up_debug (debug = context .debug )
449- if context .provider is None :
450- context .provider = provider
451- provider = DEFAULT_CAMPLY_PROVIDER if context .provider is None else context .provider
567+
452568 notifications = make_list (notifications )
453569 _validate_campsites (
454570 rec_area = rec_area ,
@@ -470,8 +586,8 @@ def campsites(
470586 provider , provider_kwargs , search_kwargs = yaml_utils .yaml_file_to_arguments (
471587 file_path = yaml_config
472588 )
473- else :
474589 provider = provider .lower ()
590+ else :
475591 search_window = SearchWindow (
476592 start_date = datetime .strptime (start_date , "%Y-%m-%d" ),
477593 end_date = datetime .strptime (end_date , "%Y-%m-%d" ),
@@ -483,9 +599,10 @@ def campsites(
483599 campsites = make_list (campsite ),
484600 weekends_only = weekends ,
485601 nights = int (nights ),
486- equipment = make_list (equipment ),
487602 offline_search = offline_search ,
488603 offline_search_path = offline_search_path ,
604+ equipment = equipment ,
605+ equipment_id = equipment_id ,
489606 )
490607 search_kwargs = dict (
491608 log = True ,
@@ -498,7 +615,7 @@ def campsites(
498615 )
499616 provider_class = {
500617 key .lower (): value for key , value in CAMPSITE_SEARCH_PROVIDER .items ()
501- }[provider . lower () ]
618+ }[provider ]
502619 camping_finder = provider_class (** provider_kwargs )
503620 camping_finder .get_matching_campsites (** search_kwargs )
504621
0 commit comments