def tell_command(self, query=None): return [ Item('Open config file...', arg='open|' + self.config_file, valid=True), Item('Open debug log...', arg='open|' + self.log_file, valid=True), ]
def tell_weather(self, ignored): '''Tell the current weather and a short forecast''' LOG.debug('telling weather') units = self.nest.scale.lower() def new_forecast(title, info): tcond = info['condition'].capitalize() thi = info['temp_high_' + units] tlo = info['temp_low_' + units] item = Item(u'%s: %s' % (title, tcond), subtitle=u'High: {0:.1f}°{1}, ' u'Low: {2:.1f}°{1}'.format( thi, units.upper(), tlo)) return item data = self.nest.structure.weather conditions = data['current']['condition'].capitalize() temp = data['current']['temp_' + units] humidity = data['current']['humidity'] items = [] item = Item(u'Now: %s' % conditions) item.subtitle = u'{0:.1f}°{1}, {2:.0f}% humidity'.format( temp, units.upper(), humidity) items.append(item) items.append(new_forecast('Today', data['forecast']['daily'][0])) items.append(new_forecast('Tomorrow', data['forecast']['daily'][1])) return items
def tell_weather(self, location, prefix=None): '''Tell the current conditions and forecast for a location''' location = location.strip() weather = self._get_weather(location) items = self._show_alert_information(weather) # conditions tu = 'F' if self.config['units'] == 'us' else 'C' title = u'Currently in {0}: {1}'.format( self.config['location']['short_name'], weather['current']['weather'].capitalize()) subtitle = u'{0}°{1}, {2}% humidity'.format( int(round(weather['current']['temp'])), tu, int(round(weather['current']['humidity']))) if self.config['show_localtime']: subtitle += ', local time is {0}'.format( self._remotize_time().strftime(self.config['time_format'])) if self.config['feelslike']: subtitle = u'Feels like ' + subtitle icon = self._get_icon(weather['current']['icon']) arg = SERVICES[self.config['service']]['lib'].get_forecast_url( location) items.append( Item(title, subtitle, icon=icon, valid=True, arg=clean_str(arg))) location = '{},{}'.format(self.config['location']['latitude'], self.config['location']['longitude']) # forecast days = self._get_days(weather) sunset = weather['info']['sunset'] today_word = self._get_today_word(sunset).capitalize() for day in days: day_desc = self._get_day_desc(day['date'], today_word) title = u'{}: {}'.format(day_desc, day['conditions'].capitalize()) subtitle = u'High: {}°{}, Low: {}°{}'.format( day['temp_hi'], tu, day['temp_lo'], tu) if 'precip' in day: subtitle += u', Precip: {}%'.format(day['precip']) arg = SERVICES[self.config['service']]['lib'].get_forecast_url( location, day['date']) icon = self._get_icon(day['icon']) items.append( Item(title, subtitle, icon=icon, arg=clean_str(arg), valid=True)) items.append(self._get_copyright_info(weather)) return items
def tell_about(self, name, query='', prefix=None): import sys import json with open('update.json', 'rt') as uf: data = json.load(uf) items = [Item('Version: {0}'.format(data['version']))] py_ver = 'Python: {:08X}'.format(sys.hexversion) items.append(Item(py_ver)) return items
def tell_start(self, query): LOG.info('adding a new time entry...') items = [] desc = query.strip() if desc: items.append( Item('Creating timer "{0}"...'.format(desc), arg='start|' + desc, valid=True)) else: items.append(Item('Waiting for description...')) return items
def _show_alert_information(self, weather): items = [] if 'alerts' in weather: for alert in weather['alerts']: item = Item(alert['description'], icon='error.png') if alert['expires']: item.subtitle = 'Expires at {}'.format(alert['expires'].strftime( self.config['time_format'])) if 'uri' in alert: item.arg = clean_str(alert['uri']) item.valid = True items.append(item) return items
def _show_alert_information(self, weather): items = [] if 'alerts' in weather: for alert in weather['alerts']: item = Item(alert['description'], icon='error.png') if alert['expires']: item.subtitle = 'Expires at {}'.format( alert['expires'].strftime(self.config['time_format'])) if 'uri' in alert: item.arg = clean_str(alert['uri']) item.valid = True items.append(item) return items
def tell_start(self, query): LOG.info('tell_start(%s)', query) items = [] desc = query.strip() if desc: items.append( Item('Creating timer "{0}"...'.format(desc), arg='start|' + desc, valid=True)) LOG.debug('created item %s', items[-1]) else: items.append(Item('Waiting for description...')) return items
def tell_commands(self, query): LOG.info('telling cmd with "{0}"'.format(query)) items = [] items.append( Item('Open toggl.com', arg='open|https://new.toggl.com/app', subtitle='Open a browser tab for toggl.com', valid=True)) items.append( Item('Open the workflow config file', arg='open|' + self.config_file, subtitle='Change workflow options here, like the ' 'debug log level', valid=True)) items.append( Item('Open the debug log', arg='open|' + self.log_file, subtitle='Open a browser tab for toggl.com', valid=True)) if self.config['use_notifier']: items.append( Item('Disable the menubar notifier', subtitle='Exit and disable the menubar notifier', arg='disable_notifier', valid=True)) else: items.append( Item('Enable the menubar notifier', subtitle='Start and enable the menubar notifier', arg='enable_notifier', valid=True)) items.append( Item('Clear the cache', subtitle='Force a cache refresh on the next query', arg='force_refresh', valid=True)) if 'api_key' in self.config: items.append( Item('Forget your API key', subtitle='Forget your stored API key, allowing ' 'you to change it', arg='clear_key', valid=True)) if len(query.strip()) > 1: # there's a filter items = self.fuzzy_match_list(query.strip(), items, key=lambda t: t.title) if len(items) == 0: items.append(Item("Invalid command")) return items
def tell_location(self, query, prefix=None): items = [] query = query.strip() if len(query) > 0: results = wunderground.autocomplete(query) for result in [r for r in results if r['type'] == 'city']: items.append( Item(result['name'], arg='location|' + result['name'], valid=True)) else: items.append(Item('Enter a location...')) return items
def _convert(self, query): query = query.strip() try: value, text = self.converter.convert(query) except Exception, e: LOG.exception('Error converting') if e.message.startswith('Parse error in query'): return [Item('Waiting for input...')] else: try: int(e.message) return [Item('Waiting for input...')] except: pass raise e
def tell_feelslike(self, query, prefix=None): feelslike = self.config.get('feelslike', False) return [ Item('Toggle whether to show "feels like" temperatures', subtitle='Current value is ' + str(feelslike).lower(), arg='feelslike', valid=True) ]
def tell_convert(self, query): '''Perform a simple conversion query.''' LOG.debug('called with query "%s"', query) try: return self._convert(query) except: return [Item('Waiting for input...')]
def tell_icons(self, ignored): items = [] sets = [f for f in os.listdir('icons') if not f.startswith('.')] for iset in sets: uid = 'icons-{}'.format(iset) icon = 'icons/{}/{}.png'.format(iset, EXAMPLE_ICON) title = iset.capitalize() item = Item(title, uid=uid, icon=icon, arg=u'icons|' + iset, valid=True) info_file = os.path.join('icons', iset, 'info.json') if os.path.exists(info_file): with open(info_file, 'rt') as ifile: info = json.load(ifile) if 'description' in info: item.subtitle = info['description'] items.append(item) return items
def _get_copyright_info(self, weather): arg = SERVICES[self.config['service']]['url'] time = weather['info']['time'].strftime(self.config['time_format']) return Item(LINE, u'Fetched from {} at {}'.format( SERVICES[self.config['service']]['name'], time), icon='blank.png', arg=arg, valid=True)
def tell_target(self, temp): '''Tell the target temperature''' LOG.debug('telling target temperature') target = self.nest.target_temperature temp = temp.strip() temp = temp.strip('"') temp = temp.strip("'") units = self.nest.scale.upper() if temp: if self.nest.mode == 'range': temps = temp.split() if len(temps) != 2: return [Item('Waiting for valid input...')] for t in temps: if len(t) == 1 or len(t) > 2: return [Item('Waiting for valid input...')] return [Item(u'Set temperature range to %.1f°%s - %.1f°%s' % ( float(temps[0]), units, float(temps[1]), units), arg=temp, valid=True)] else: return [Item(u'Set temperature to %.1f°%s' % (float(temp), units), arg=temp, valid=True)] else: if self.nest.mode == 'range': item = Item( u'Target temperature range is %.1f°%s - ' u'%.1f°%s' % (target[0], units, target[1], units)) else: item = Item( u'Target temperature: %.1f°%s' % (target, units)) if self.nest.mode == 'range': item.subtitle = (u'Enter a temperature range in °%s to update; ' u'use format "low high"' % units) else: item.subtitle = u'Enter a temperature in °%s to update' % units return [item]
def tell_icons(self, ignored, prefix=None): items = [] sets = [f for f in os.listdir('icons') if not f.startswith('.')] for iset in sets: uid = 'icons-{}'.format(iset) icon = 'icons/{}/{}.png'.format(iset, EXAMPLE_ICON) title = iset.capitalize() item = Item(title, uid=uid, icon=icon, arg=u'icons|' + iset, valid=True) info_file = os.path.join('icons', iset, 'info.json') if os.path.exists(info_file): with open(info_file, 'rt') as ifile: info = json.load(ifile) if 'description' in info: item.subtitle = info['description'] items.append(item) return items
def tell_format(self, fmt, prefix=None): items = [] now = datetime.now() if fmt: try: items.append( Item(now.strftime(fmt), arg='format|' + fmt, valid=True)) except: items.append(Item('Waiting for input...')) items.append( Item('Python time format syntax...', arg='http://docs.python.org/2/library/' 'datetime.html#strftime-and-strptime-' 'behavior', valid=True)) else: for fmt in TIME_FORMATS: items.append( Item(now.strftime(fmt), arg='format|' + fmt, valid=True)) return items
def tell_units(self, arg, prefix=None): arg = arg.strip() items = [ Item('US', u'US units (°F, in, mph)', arg='units|us', autocomplete='options units US', valid=True), Item('SI', u'SI units (°C, cm, kph)', arg='units|si', autocomplete='options units SI', valid=True) ] items = self.partial_match_list(arg, items, key=lambda t: t.title) if len(items) == 0: items.append(Item('Invalid units')) return items
def tell_status(self, ignore): '''Tell the Nest's overall status''' LOG.debug('telling status') temp = self.nest.temperature target = self.nest.target_temperature humidity = self.nest.humidity away = 'yes' if self.nest.structure.away else 'no' fan = self.nest.fan units = self.nest.scale.upper() item = Item(u'Temperature: %.1f°%s' % (temp, units)) if self.nest.mode == 'range': target = u'Heat/cool to %.1f°%s - %.1f°%s' % ( target[0], units, target[1], units) elif self.nest.mode == 'heat': target = u'Heating to %.1f°%s' % (target, units) else: target = u'Cooling to %.1f°%s' % (target, units) item.subtitle = u'%s Humidity: %.1f%% Fan: %s Away: %s' % ( target, humidity, fan, away) return [item]
def tell_on(self, query): '''Return info about entries over a span A span may be: - a single start date, which denotes a span from that date to now - one of {'today', 'yesterday', 'this week'} - a week day name ''' query = query.strip() if not query: return [ Item('Enter a date', subtitle='9/8, yesterday, monday, ' '...') ] return self.tell_query('', start=get_start(query), end=get_end(query))
def tell_since(self, query): '''Return info about entries since a time A time may be: - a date - one of {'today', 'yesterday', 'this week'} ''' query = query.strip() if not query: return [ Item('Enter a start time', subtitle='This can be a time, ' 'date, datetime, "yesterday", "tuesday", ...') ] return self.tell_query('', start=get_start(query))
def tell_days(self, days, prefix=None): if len(days) == 0: length = '{} day'.format(self.config['days']) if self.config['days'] != 1: length += 's' return [ Item( 'Enter the number of forecast days to show...', subtitle='Currently showing {} of forecast'.format(length)) ] else: days = int(days) if days < 0 or days > 10: raise Exception('Value must be between 1 and 10') length = '{} day'.format(days) if days != 1: length += 's' return [ Item('Show {} of forecast'.format(length), arg='days|{0}'.format(days), valid=True) ]
def tell_service(self, query, prefix=None): items = [] query = query.strip() for svc in SERVICES.keys(): items.append( Item(SERVICES[svc]['name'], uid=svc, arg='service|' + svc, valid=True)) if len(query) > 0: q = query.lower() items = [i for i in items if q in i.title.lower()] return items
def tell_help(self, query): items = [] items.append( Item("Use '/' to list existing timers", subtitle='Type some text to filter the results')) items.append( Item("Use '<' to list timers started since a time", subtitle='9/2, 9/2/13, 2013-9-2T22:00-04:00, ...')) items.append( Item("Use '#' to list time spent on a particular date", subtitle='9/2, 9/2/13, 2013-9-2T22:00-04:00, ...')) items.append( Item("Use '+' to start a new timer", subtitle="Type a description after the '+'")) items.append(Item("Use '.' to stop the current timer")) items.append( Item("Use '>' to access other commands", subtitle='Enable menubar icon, go to toggl.com, ' '...')) items.append(Item("Select an existing timer to toggle it")) return items
def tell_help(self, query): items = [] items.append( Item("Use '/' to list existing timers", subtitle='Type some text to filter the results')) items.append( Item("Use '//' to force a cache refresh", subtitle='Data from Toggl is normally cached for ' '{0} seconds'.format(CACHE_LIFETIME))) items.append( Item("Use '<' to list timers started since a time", subtitle='9/2, 9/2/13, 2013-9-2T22:00-04:00, ...')) items.append( Item("Use '@' to list time spent on a particular date", subtitle='9/2, 9/2/13, 2013-9-2T22:00-04:00, ...')) items.append( Item("Use '+' to start a new timer", subtitle="Type a description after the '+'")) items.append( Item("Use '>' to access other commands", subtitle='Enable menubar icon, go to toggl.com, ' '...')) items.append(Item("Select an existing timer to toggle it")) return items
class UnitsWorkflow(Workflow): def __init__(self): super(UnitsWorkflow, self).__init__() self.currencies_file = os.path.join(self.cache_dir, 'currencies.txt') self.load_currencies() self.separator = self.config.get('separator') or '>' self.precision = self.config.get('precision') or None self.config['separator'] = self.separator self.config['precision'] = self.precision self.converter = Converter(self.currencies_file, separator=self.separator, precision=self.precision) def load_currencies(self): import datetime lines = [] last_refresh = None today = datetime.date.today() if os.path.exists(self.currencies_file): with open(self.currencies_file, 'rt') as cf: lines = cf.readlines() if len(lines) > 0 and lines[0].startswith('# date:'): last_refresh = lines[0].split(':')[1].strip() last_refresh = datetime.datetime.strptime(last_refresh, '%Y-%m-%d').date() if not last_refresh or last_refresh != today: import yfinance rates = yfinance.get_rates(CURRENCIES) with open(self.currencies_file, 'wt') as cf: cf.write('# date: ' + today.strftime('%Y-%m-%d') + '\n') cf.write('USD = [currency] = usd\n') for k, v in rates.items(): cf.write('{0} = USD / {1} = {2}\n'.format(k, v, k.lower())) def _convert(self, query): query = query.strip() try: value, text = self.converter.convert(query) except Exception, e: LOG.exception('Error converting') if e.message.startswith('Parse error in query'): return [Item('Waiting for input...')] else: try: int(e.message) return [Item('Waiting for input...')] except: pass raise e return [ Item(text, arg=str(value), valid=True, subtitle='Action this item to copy %s to the ' 'clipboard' % value) ]
def tell_query(self, query, start=None, end=None): '''List entries that match a query. Note that an end time without a start time will be ignored.''' LOG.info('tell_query("{0}", start={1}, end={2})'.format( query, start, end)) if not start: end = None needs_refresh = False query = query.strip() if self.cache.get('disable_cache', False): LOG.debug('cache is disabled') needs_refresh = True elif self.cache.get('time') and self.cache.get('time_entries'): last_load_time = self.cache.get('time') LOG.debug('last load was %s', last_load_time) import time now = int(time.time()) if now - last_load_time > CACHE_LIFETIME: LOG.debug('automatic refresh') needs_refresh = True else: LOG.debug('cache is missing timestamp or data') needs_refresh = True if needs_refresh: LOG.debug('refreshing cache') try: all_entries = toggl.TimeEntry.all() except Exception: LOG.exception('Error getting time entries') raise Exception('Problem talking to toggl.com') import time self.cache['time'] = int(time.time()) self.cache['time_entries'] = serialize_entries(all_entries) else: LOG.debug('using cached data') all_entries = deserialize_entries(self.cache['time_entries']) LOG.debug('%d entries', len(all_entries)) if start: LOG.debug('filtering on start time %s', start) if end: LOG.debug('filtering on end time %s', end) all_entries = [ e for e in all_entries if e.start_time < end and e.stop_time > start ] else: all_entries = [e for e in all_entries if e.stop_time > start] LOG.debug('filtered to %d entries', len(all_entries)) efforts = {} # group entries with the same description into efforts (so as not to be # confused with Toggl tasks for entry in all_entries: if entry.description not in efforts: efforts[entry.description] = Effort(entry.description, start, end) efforts[entry.description].add(entry) efforts = efforts.values() efforts = sorted(efforts, reverse=True, key=lambda e: e.newest_entry.start_time) items = [] if start: if len(efforts) > 0: seconds = sum(e.seconds for e in efforts) LOG.debug('total seconds: %s', seconds) total_time = to_hours_str(seconds) if end: item = Item('{0} hours on {1}'.format( total_time, start.date().strftime(DATE_FORMAT)), subtitle=Item.LINE) else: item = Item('{0} hours from {1}'.format( total_time, start.date().strftime(DATE_FORMAT)), subtitle=Item.LINE) else: item = Item('Nothing to report') items.append(item) show_suffix = start or end for effort in efforts: item = Item(effort.description, valid=True) now = LOCALTZ.localize(datetime.datetime.now()) newest_entry = effort.newest_entry if newest_entry.is_running: item.icon = 'running.png' started = newest_entry.start_time delta = to_approximate_time(now - started) seconds = effort.seconds total = '' if seconds > 0: hours = to_hours_str(seconds, show_suffix=show_suffix) total = ' ({0} hours total)'.format(hours) item.subtitle = 'Running for {0}{1}'.format(delta, total) item.arg = 'stop|{0}|{1}'.format(newest_entry.id, effort.description) else: seconds = effort.seconds hours = to_hours_str(datetime.timedelta(seconds=seconds), show_suffix=show_suffix) if start: item.subtitle = ('{0} hours'.format(hours)) else: stop = newest_entry.stop_time if stop: delta = to_approximate_time(now - stop, ago=True) else: delta = 'recently' oldest = effort.oldest_entry since = oldest.start_time since = since.strftime('%m/%d') item.subtitle = ('{0} hours since {1}, ' 'stopped {2}'.format(hours, since, delta)) item.arg = 'continue|{0}|{1}'.format(newest_entry.id, effort.description) items.append(item) if len(query.strip()) > 1: # there's a filter test = query[1:].strip() items = self.fuzzy_match_list(test, items, key=lambda t: t.title) if len(items) == 0: items.append(Item("Nothing found")) return items
def tell_query(self, query, start=None, end=None): '''List entries that match a query. Note that an end time without a start time will be ignored.''' LOG.info('tell_query("{0}", start={1}, end={2})'.format( query, start, end)) if not start: end = None needs_refresh = False query = query.strip() if self.cache.get('disable_cache', False): LOG.debug('cache is disabled') needs_refresh = True elif self.cache.get('time') and self.cache.get('time_entries'): last_load_time = self.cache.get('time') LOG.debug('last load was %s', last_load_time) import time now = int(time.time()) if now - last_load_time > CACHE_LIFETIME: LOG.debug('automatic refresh') needs_refresh = True else: LOG.debug('cache is missing timestamp or data') needs_refresh = True if needs_refresh: LOG.debug('refreshing cache') try: all_entries = toggl.TimeEntry.all() except Exception: LOG.exception('Error getting time entries') raise Exception('Problem talking to toggl.com') import time self.cache['time'] = int(time.time()) self.cache['time_entries'] = serialize_entries(all_entries) else: LOG.debug('using cached data') all_entries = deserialize_entries(self.cache['time_entries']) LOG.debug('%d entries', len(all_entries)) if start: LOG.debug('filtering on start time %s', start) if end: LOG.debug('filtering on end time %s', end) all_entries = [e for e in all_entries if e.start_time < end and e.stop_time > start] else: all_entries = [e for e in all_entries if e.stop_time > start] LOG.debug('filtered to %d entries', len(all_entries)) efforts = {} # group entries with the same description into efforts (so as not to be # confused with Toggl tasks for entry in all_entries: if entry.description not in efforts: efforts[entry.description] = Effort(entry.description, start, end) efforts[entry.description].add(entry) efforts = efforts.values() efforts = sorted(efforts, reverse=True, key=lambda e: e.newest_entry.start_time) items = [] if start: if len(efforts) > 0: seconds = sum(e.seconds for e in efforts) LOG.debug('total seconds: %s', seconds) total_time = to_hours_str(seconds) if end: item = Item('{0} hours on {1}'.format( total_time, start.date().strftime(DATE_FORMAT)), subtitle=Item.LINE) else: item = Item('{0} hours from {1}'.format( total_time, start.date().strftime(DATE_FORMAT)), subtitle=Item.LINE) else: item = Item('Nothing to report') items.append(item) show_suffix = start or end for effort in efforts: item = Item(effort.description, valid=True) now = LOCALTZ.localize(datetime.datetime.now()) newest_entry = effort.newest_entry if newest_entry.is_running: item.icon = 'running.png' started = newest_entry.start_time delta = to_approximate_time(now - started) seconds = effort.seconds total = '' if seconds > 0: hours = to_hours_str(seconds, show_suffix=show_suffix) total = ' ({0} hours total)'.format(hours) item.subtitle = 'Running for {0}{1}'.format(delta, total) item.arg = 'stop|{0}|{1}'.format(newest_entry.id, effort.description) else: seconds = effort.seconds hours = to_hours_str(datetime.timedelta(seconds=seconds), show_suffix=show_suffix) if start: item.subtitle = ('{0} hours'.format(hours)) else: stop = newest_entry.stop_time if stop: delta = to_approximate_time(now - stop, ago=True) else: delta = 'recently' oldest = effort.oldest_entry since = oldest.start_time since = since.strftime('%m/%d') item.subtitle = ('{0} hours since {1}, ' 'stopped {2}'.format(hours, since, delta)) item.arg = 'continue|{0}|{1}'.format(newest_entry.id, effort.description) items.append(item) if len(query.strip()) > 1: # there's a filter test = query[1:].strip() items = self.fuzzy_match_list(test, items, key=lambda t: t.title) if len(items) == 0: items.append(Item("Nothing found")) return items
def _create_item(self, day_desc, content): title = u'{}: {}'.format(day_desc, content) icon = self._get_icon(ICON_NAME) return Item(title, icon=icon, valid=False)
def tell_config(self, query, prefix=None): return ([ Item('Open the config file', arg='open|' + self.config_file, valid=True) ])
def tell_log(self, query, prefix=None): return [ Item('Open the debug log', arg='open|' + self.log_file, valid=True) ]