sys.exit() from bs4 import BeautifulSoup from src.client import Client from src.request import RequestHandler from src.parser import swellParser request_handler = RequestHandler() client = Client(request_handler) region = client.regions[random.randint(0, len(client.regions) - 1)]['ref'] sub_areas = json.loads(client.getSubAreas(region)) print(region) sub_area = list(sub_areas.keys())[random.randint(0, len(sub_areas) - 1)] local_areas = json.loads(client.getLocalAreas(sub_area)) print(sub_area) local_area = list(local_areas.keys())[random.randint(0, len(local_areas) - 1)] swell_html = client.getSwellHTML(local_area) swell_soup = BeautifulSoup(swell_html, 'html5lib') swell_parser = swellParser(swell_soup) print(local_area) forecast = swell_parser.getForecast() current = swell_parser.getCurrentConditions() pprint.pprint(forecast) pprint.pprint(current)
class swellCLI: def __init__(self): # get current day of week name today = datetime.date.today() self.today_name = calendar.day_name[today.weekday()].upper() self.root_path = os.path.dirname( os.path.dirname(os.path.abspath(__file__))) # Make sure '.swell_user_data' directory exists, if not create self.user_directory = str(os.getenv("HOME")) + "/.swell_user_data" try: os.stat(self.user_directory) except: os.mkdir(self.user_directory) # Initialize data sets and swell client self.swellFile = self.user_directory + '/swell.json' self.user_data = self.read_json(self.swellFile) self.init_data = {'favorites': []} self.request_handler = RequestHandler() self.swell = Client(self.request_handler) self.nicknameList = [] self.flags = None self.arg = None self.nickname = None self.args = sys.argv self.args.pop(0) # Remove first arg (script) for simplicity def run(self): # If user data empty initialize swellFile using init_data if self.user_data is None: self.write_json(self.swellFile, self.init_data) self.user_data = self.read_json(self.swellFile) # load spot nickname list for fav in self.user_data['favorites']: self.nicknameList.append(fav['nickname']) # Set flags and arg for arg in self.args: if arg in self.nicknameList: self.nickname = arg elif arg.isdigit() and int(arg) < len(self.nicknameList): self.nickname = self.nicknameList[int( arg)] # use index for nickname if arg[0] is '-' and self.flags is None: self.flags = arg.replace('-', '') elif self.arg is None and self.nickname is None: self.arg = arg # if 'h' help flag set strictly alone if self.flags == 'h' or self.flags == 'help': sys.exit(self.getHelpView()) if self.arg is None: self.selectAndDisplay() if self.arg == 'add': self.addLocationRoutine() if self.arg == 'spots': print(self.getSpotsNicknamesView()) if self.arg == 'help': print(self.getHelpView()) if self.arg == 'remove': if self.nickname is not None: if not self.userActionConfirmation( "remove {nickname} from saved spots".format( nickname=self.nickname)): sys.exit() if self.removeFavoriteByNickname(self.nickname): print('\n ' + Colors.BOLD + self.nickname + ' has been removed.\n' + Colors.ENDC) else: print(Colors.BOLD + '\n Could not remove ' + self.nickname + ' from data store.\n' + Colors.ENDC) else: print('\n! Invalid nickname. Try again.\n') if self.arg == 'reset': if not self.userActionConfirmation('reset data stores'): sys.exit() if self.resetUserData(): print('\n ' + Colors.BOLD + 'Data has been reset.\n' + Colors.ENDC) else: print(Colors.BOLD + '\n Could not reset data.\n' + Colors.ENDC) sys.exit(0) def selectAndDisplay(self): current = None forecast = None if self.nickname is None: # - Allow user to select a location print(Colors.UNDERLINE + '\nFollow the prompts below:\n' + Colors.ENDC) region = self.getLocationInput(self.swell.regions, 'region') sub_areas = json.loads(self.swell.getSubAreas(region)) sub_area = self.getLocationInput(sub_areas, 'sub area') local_areas = json.loads(self.swell.getLocalAreas(sub_area)) local_area = self.getLocationInput(local_areas, 'local area') else: print('') local_area = self.getLocalLinkByNickname(self.nickname) # - Display current conditions and forecast swell_html = self.swell.getSwellHTML(local_area) swell_soup = BeautifulSoup(swell_html, 'lxml') swell_parser = swellParser(swell_soup) if self.flags is None or (self.flags is not None and 'c' in self.flags): current = self.getCurrentView(swell_parser.getCurrentConditions()) elif self.flags is not None and ('c' not in self.flags and 'f' not in self.flags): current = self.getCurrentView(swell_parser.getCurrentConditions()) if self.flags is not None and 'f' in self.flags: forecast = self.getForecastView(swell_parser.getForecast()) if current is not None: print(current) if forecast is not None: print(forecast) def resetUserData(self): if not self.write_json(self.swellFile, self.init_data): return False return True def removeFavoriteByNickname(self, nickname): if not self.nicknameIsTaken(nickname): return False for i, fav in enumerate(self.user_data['favorites']): if fav['nickname'] == nickname: if not self.user_data['favorites'].pop(i): return False if not self.write_json(self.swellFile, self.user_data): return False return True return False def getLocalLinkByNickname(self, nickname): for fav in self.user_data['favorites']: if nickname == fav['nickname']: return fav['link'] return None def addLocationRoutine(self): print(Colors.UNDERLINE + '\nFollow the prompts below:\n' + Colors.ENDC) region = self.getLocationInput(self.swell.regions, 'region') sub_areas = json.loads(self.swell.getSubAreas(region)) sub_area = self.getLocationInput(sub_areas, 'sub area') local_areas = json.loads(self.swell.getLocalAreas(sub_area)) local_area = self.getLocationInput(local_areas, 'local area') try: self.user_data['favorites'].append({ 'link': local_area, 'title': local_areas[local_area]['label'], 'nickname': '' }) nickname = self.getNicknameRoutine(self.user_data['favorites'][-1]) self.user_data['favorites'][-1]['nickname'] = nickname if (self.write_json(self.swellFile, self.user_data)): print(Colors.BOLD + '\nSpot added!' + Colors.ENDC) except Exception as e: print(e) return False print('\nYou can now easily check the conditions and forecast for ' + \ Colors.BOLD + local_areas[local_area]['label'] + Colors.ENDC + ' using the nickname ' + \ Colors.BOLD + nickname + Colors.ENDC + ' as a command line argument.\n') return True def getNicknameRoutine(self, spot): print( Colors.UNDERLINE + 'Add a nickname for this spot. (no spaces, limit 20 characters)\n' + Colors.ENDC) while 1: user_input = input(Colors.BOLD + 'Nickname for ' + spot['title'] + Colors.ENDC + ': ') if len(user_input) > 20 or ' ' in user_input: print('\n! Invalid nickname. Try again.\n') continue if self.nicknameIsTaken(user_input): print('\n! That nickname already exists. Try again\n') continue return user_input def getLocationInput(self, data_list, selection_str): while 1: refs = [] for i, item in enumerate(data_list): refs.append(item) print(' ' + Colors.BOLD + '{0: <3}'.format(str(i)) + Colors.ENDC + \ Colors.CYAN + ' --> ' + Colors.ENDC + ' ' + data_list[item]['label']) user_input = input('\nSelect a ' + selection_str + ' (' + Colors.BOLD + '0-' + str(len(data_list) - 1) + Colors.ENDC + ')' + ': ') try: if int(user_input) >= 0 and int( user_input) <= len(data_list) - 1: print('Selected ' + Colors.BOLD + data_list[refs[int(user_input)]]['label'] + Colors.ENDC + '\n') return refs[int(user_input)] except: pass print(Colors.RED + '\nTry again. Input must be an integer from 0-' + str(len(data_list) - 1) + '\n' + Colors.ENDC) def userActionConfirmation(self, action_text): user_input = input( '\n{bold}Are you sure you want to {action}? (Y/n){bold_end}: '. format(action=action_text, bold=Colors.BOLD, bold_end=Colors.ENDC)) if user_input is 'Y': return True else: return False def nicknameIsTaken(self, nickname): for i, spot in enumerate(self.user_data['favorites']): if nickname == spot['nickname']: return True return False def getCurrentView(self, data): col_pad = 4 table_pad = 2 location = data['location_title'].split(',') city = location[0].title() state = location[1].upper() air = ['Air', data['air'], data['wind']] swell = ['Swell', data['buoy_name'], data['wave_height']] tide = [ 'Tide', str('low: ' + data['low_tide']), str('high: ' + data['high_tide']) ] water = ['Water', data['water_temp'], data['wetsuit']] air_w = len(max(air, key=len)) + col_pad swell_w = len(max(swell, key=len)) + col_pad tide_w = len(max(tide, key=len)) + col_pad water_w = len(max(water, key=len)) + col_pad table_w = air_w + swell_w + tide_w + water_w + col_pad view = '\n' view += Colors.OKYELLOW + str('Current conditions for ' + Colors.BOLD + city + ',' + state).ljust( table_w) + Colors.ENDC + '\n\n' view += ''.ljust(table_pad) + Colors.CYAN + Colors.BOLD + air[0].ljust( air_w) + swell[0].ljust(swell_w) + tide[0].ljust( tide_w) + water[0].ljust(water_w) + Colors.ENDC + '\n' view += ''.ljust(table_pad) + air[1].ljust(air_w) + swell[1].ljust( swell_w) + tide[1].ljust(tide_w) + water[1].ljust(water_w) + '\n' view += ''.ljust(table_pad) + air[2].ljust(air_w) + swell[2].ljust( swell_w) + tide[2].ljust(tide_w) + water[2].ljust(water_w) + '\n' view += '\n' return view def getForecastView(self, data): table_pad = 2 col_w = 32 table_w = col_w * 2 location = data['location_title'].split(',') city = location[0].title() state = location[1].upper() forecast_days = data['forecast'] view = '\n' view += Colors.OKYELLOW + 'Long range forecast for ' + Colors.BOLD + city + ',' + state + Colors.ENDC + '\n\n' for i, day in enumerate(forecast_days): day_str = Colors.BOLD + day['day_of_week'] + Colors.ENDC # this seems to no longer be needed #if day['day_of_week'] == self.today_name: # day_str += ' (' + Colors.OKYELLOW + 'TODAY' + Colors.ENDC + ')' am_color = Colors.WHITE pm_color = Colors.WHITE if 'FAIR' in day['am_conditions']: am_color = Colors.OKBLUE elif 'CHOPPY' in day['am_conditions']: am_color = Colors.RED elif 'CLEAN' in day['am_conditions']: am_color = Colors.OKGREEN if 'FAIR' in day['pm_conditions']: pm_color = Colors.OKBLUE elif 'CHOPPY' in day['pm_conditions']: pm_color = Colors.RED elif 'CLEAN' in day['pm_conditions']: pm_color = Colors.OKGREEN #print(self.breakDownLongText(day['conditions_long_text'], table_w)) am_str = str(am_color + 'AM: ' + day['am_height'] + Colors.ENDC).ljust(col_w) pm_str = str(pm_color + 'PM: ' + day['pm_height'] + Colors.ENDC).ljust(col_w) view += ''.ljust(table_pad) + day_str + '\n' view += ''.ljust(table_pad) + am_str view += ''.ljust(table_pad) + pm_str view += '\n' if len(day['conditions_long_text']) > 4: for line in self.breakDownLongText(day['conditions_long_text'], table_w): view += ''.ljust(table_pad) + line + '\n' view += '\n' if len(day['surf']) > 4: for line in self.breakDownLongText(day['surf'], table_w): view += ''.ljust(table_pad) + line + '\n' view += '\n' view += '\n' return view def getSpotsNicknamesView(self): view = '\n' view += Colors.BOLD + 'Your Saved Spots' + Colors.ENDC + ':\n\n' no_spots = False try: if len(self.user_data['favorites']) == 0: no_spots = True except IndexError: no_spots = True if no_spots: view += ' -- None available --\n\n' return view for i, fav in enumerate(self.user_data['favorites']): view += ' ' + Colors.CYAN + Colors.BOLD + '{0: <20}'.format( fav['nickname']) + Colors.ENDC + ' (' + str( i) + ' - ' + fav['title'] + ')\n' view += '\n' view += ' * Use these nicknames or indexes as a command line argument to quickly retrieve the surf report.\n' return view def getHelpView(self): table_pad = 2 col_pad = 20 view = '\n' view += Colors.BOLD + 'MANUAL:' + Colors.ENDC + '\n\n' view += ''.ljust( table_pad ) + Colors.UNDERLINE + Colors.OKYELLOW + 'COMMANDS:' + Colors.ENDC + '\n' view += ''.ljust(table_pad) + Colors.BOLD + '[no argument]'.ljust(col_pad) + Colors.ENDC + \ '- Prompts user to select a location and by default will display \n' + ''.ljust(col_pad+4) + 'the current conditions (unless other flags are specified).\n' view += ''.ljust(table_pad) + Colors.BOLD + '[nickname]'.ljust(col_pad) + Colors.ENDC + \ '- Will display either the current conditions, forecast, or both \n' + ''.ljust(col_pad+4) + '(depending on specified flags) for the given nickname.\n' view += ''.ljust(table_pad) + Colors.BOLD + 'spots'.ljust(col_pad) + Colors.ENDC + \ '- Displays users saved spots by nickname.\n' view += ''.ljust(table_pad) + Colors.BOLD + 'add'.ljust(col_pad) + Colors.ENDC + \ '- Prompts user to add/save a new spot to user data store.\n' view += ''.ljust(table_pad) + Colors.BOLD + 'remove [nickname]'.ljust(col_pad) + Colors.ENDC + \ '- Removes saved spot from user data store if [nickname] is a \n' + ''.ljust(col_pad+4) + 'valid nickname or spot index.\n' view += ''.ljust(table_pad) + Colors.BOLD + 'reset'.ljust(col_pad) + Colors.ENDC + \ '- Resets user data store to original state.\n\n' view += ''.ljust( table_pad ) + Colors.UNDERLINE + Colors.OKYELLOW + 'FLAGS:' + Colors.ENDC + '\n' view += ''.ljust(table_pad) + Colors.BOLD + '-c'.ljust(col_pad) + Colors.ENDC + \ '- Current conditions\n' view += ''.ljust(table_pad) + Colors.BOLD + '-f'.ljust(col_pad) + Colors.ENDC + \ '- Forecast\n' view += ''.ljust(table_pad) + Colors.BOLD + '-h, --help'.ljust( col_pad) + Colors.ENDC + '- Displays manual\n' view += '\n' view += ''.ljust( table_pad ) + Colors.BOLD + "*" + Colors.ENDC + " '-h' and '--help' are only interpreted if used exclusively.\n" view += ''.ljust( table_pad ) + Colors.BOLD + "*" + Colors.ENDC + " Other flags can be used in combination i.e. -fc, -cf.\n\n" return view def breakDownLongText(self, text, chunk_size): if len(text) == 0: return [] elif len(text) <= chunk_size: return [text] s = text.split(' ') line = '' text_chunks = [] for i, word in enumerate(s): redo = True while redo: if (len(line) + len(word) + len(' ')) < chunk_size: line += str(word + ' ') redo = False else: text_chunks.append(line) line = '' if i == len(s) - 1: text_chunks.append(line) return text_chunks def write_json(self, filename, data): try: with open(filename, 'w+') as outfile: json.dump(data, outfile) outfile.close() return True except: return False def read_json(self, filename): try: with open(filename) as json_data: d = json.load(json_data) json_data.close() return d except json.JSONDecodeError: return None except FileNotFoundError: return None