/usr/bin/hamster-cli is in hamster-applet 2.91.3+git20120514.b9fec3e1-1.
This file is owned by root:root, with mode 0o755.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 | #!/usr/bin/env python
# - coding: utf-8 -
# Copyright (C) 2010 Matías Ribecky <matias at mribecky.com.ar>
# Copyright (C) 2010 Toms Bauģis <toms.baugis@gmail.com>
# This file is part of Project Hamster.
# Project Hamster is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# Project Hamster is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with Project Hamster. If not, see <http://www.gnu.org/licenses/>.
'''A script to control the applet from the command line.'''
import sys, os
import optparse
import re
import datetime as dt
from hamster import client
from hamster.lib import stuff
class ConfigurationError(Exception):
'''An error of configuration.'''
pass
class HamsterClient(object):
'''The main application.'''
def __init__(self):
self.storage = client.Storage()
def toggle(self):
self.storage.toggle()
def start_tracking(self, activity, start_time = None, end_time = None):
'''Start a new activity.'''
self.storage.add_fact(stuff.Fact(activity,
start_time = start_time,
end_time = end_time))
def stop_tracking(self):
'''Stop tracking the current activity.'''
self.storage.stop_tracking()
def list(self, start_time = None, end_time = None):
'''Print a listing of activities.'''
start_time = start_time or dt.datetime.combine(dt.date.today(), dt.time())
end_time = end_time or start_time.replace(hour=23, minute=59, second=59)
headers = {'activity': _("Activity"),
'category': _("Category"),
'tags': _("Tags"),
'start': _("Start"),
'end': _("End"),
'duration': _("Duration")}
line_fmt = ' %*s - %*s (%*s) | %s@%s %s'
print_with_date = start_time.date() != start_time.date()
if print_with_date:
dates_align_width = len('xxxx-xx-xx xx:xx')
else:
dates_align_width = len('xx:xx')
column_width = {'start': max(len(headers['start']), dates_align_width),
'end': max(len(headers['end']), dates_align_width),
'duration': max(len(headers['duration']), 7)}
print line_fmt % (column_width['start'], headers['start'],
column_width['end'], headers['end'],
column_width['duration'], headers['duration'],
headers['activity'],
headers['category'],
headers['tags'])
first_column_width = (8 + sum(column_width.values()))
second_column_width = 4 + len(headers['activity']) + \
len(headers['category']) + \
len(headers['tags'])
print "%s+%s" % ('-' * first_column_width, '-' * second_column_width)
for fact in self.storage.get_facts(start_time, end_time, ""):
if fact.start_time < start_time or fact.start_time > end_time:
# Hamster returns activities for the whole day, not just the
# time range we sent
# TODO - why should that be a problem? /toms/
continue
fact_data = fact_dict(fact, print_with_date)
print line_fmt % (column_width['start'], fact_data['start'],
column_width['end'], fact_data['end'],
column_width['duration'], fact_data['duration'],
fact_data['activity'],
fact_data['category'],
fact_data['tags'])
def list_activities(self):
'''Print the names of all the activities.'''
for activity in self.storage.get_activities():
print '%s@%s' % (activity['name'].encode('utf8'), activity['category'].encode('utf8'))
def list_categories(self):
'''Print the names of all the categories.'''
for category in self.storage.get_categories():
print category['name'].encode('utf8')
def parse_datetime_range(time):
'''Parse starting and ending datetime separated by a '-'.'''
start_time, remainder = parse_datetime(time)
end_time = None
if remainder and remainder.startswith("-"):
end_time, remainder = parse_datetime(remainder[1:])
return start_time, end_time
_DATETIME_PATTERN = ('^((?P<relative>-)?|'
'(?P<year>\d{4})'
'(-(?P<month>\d{1,2})'
'(-(?P<day>\d{1,2}))?)? )?'
'((?P<hour>\d{1,2}):)?'
'(?P<minute>\d{1,2})'
'(:(?P<second>\d{1,2}))?'
'(?P<rest>\D.+)?$')
_DATETIME_REGEX = re.compile(_DATETIME_PATTERN)
def parse_datetime(arg):
'''Parse a date and time.'''
match = _DATETIME_REGEX.match(arg)
if not match:
return dt.datetime.now(), arg
if match.groupdict()['relative']:
hour = int(match.groupdict()['hour'] or 0)
minute = int(match.groupdict()['minute'])
second = int(match.groupdict()['second'] or 0)
time_ago = dt.timedelta(hours=hour,
minutes=minute,
seconds=second)
rest = (match.groupdict()['rest'] or '').strip()
return dt.datetime.now() - time_ago, rest
else:
date = dt.datetime.now().date()
try:
if match.groupdict()['year']:
date = date.replace(year=int(match.groupdict()['year']))
if match.groupdict()['month']:
date = date.replace(month=int(match.groupdict()['month']))
if match.groupdict()['day']:
date = date.replace(day=int(match.groupdict()['day']))
time = dt.time(hour=int(match.groupdict()['hour'] or 0),
minute=int(match.groupdict()['minute']),
second=int(match.groupdict()['second'] or 0))
except ValueError, err:
if match.groupdict()['rest']:
date_str = arg[:-len(match.groupdict()['rest'])]
else:
date_str = arg
raise ConfigurationError(_("invalid date/time '%s'" % date_str))
rest = (match.groupdict()['rest'] or '').strip()
return dt.datetime.combine(date, time), rest
def fact_dict(fact_data, with_date):
fact = {}
if with_date:
fmt = '%Y-%m-%d %H:%M'
else:
fmt = '%H:%M'
fact['start'] = fact_data.start_time.strftime(fmt)
if fact_data.end_time:
fact['end'] = fact_data.end_time.strftime(fmt)
else:
end_date = dt.datetime.now()
fact['end'] = ''
fact['duration'] = stuff.format_duration(fact_data.delta)
fact['activity'] = fact_data.activity
fact['category'] = fact_data.category
if fact_data.tags:
fact['tags'] = ' '.join('#%s' % tag for tag in fact_data.tags)
else:
fact['tags'] = ''
return fact
if __name__ == '__main__':
from hamster.lib import i18n
i18n.setup_i18n()
usage = _(
"""Client for controlling the hamster-applet. Usage:
%(prog)s start ACTIVITY [START_TIME[-END_TIME]]
%(prog)s stop
%(prog)s list [START_TIME[-END_TIME]]
Actions:
* start (default): Start tracking an activity.
* stop: Stop tracking current activity.
* list: List activities.
* list-activities: List all the activities names, one per line.
* list-categories: List all the categories names, one per line.
Time formats:
* 'YYYY-MM-DD hh:mm:ss': Absolute time. Defaulting to 0 for the time
values missing, and current day for date values.
E.g. (considering 2010-03-09 16:30:20 as current date, time):
2010-03 13:15:40 is 2010-03-09 13:15:40
2010-03-09 13:15 is 2010-03-09 13:15:00
2010-03-09 13 is 2010-03-09 00:13:00
2010-02 13:15:40 is 2010-02-09 13:15:40
13:20 is 2010-03-09 13:20:00
20 is 2010-03-09 00:20:00
* '-hh:mm:ss': Relative time. From the current date and time. Defaulting
to 0 for the time values missing, same as in absolute time.
""")
# CLI Structure: ./hamster-cli.py <start|stop|list|...> <conditional_args>
if len(sys.argv) < 2:
sys.exit(usage % {'prog': sys.argv[0]})
command, args = sys.argv[1], sys.argv[2:]
if command in ("toggle", "start", "stop", "list", "list-activities", "list-categories"):
hamster_client = HamsterClient()
if command == 'toggle':
hamster_client.toggle()
elif command == 'start':
if not args: # mandatory is only the activity name
sys.exit(usage % {'prog': sys.argv[0]})
activity = args[0]
start_time, end_time = None, None
if len(args) > 1:
start_time, end_time = parse_datetime_range(args[1])
if start_time > dt.datetime.now() or (end_time and end_time > dt.datetime.now()):
sys.exit("Activity must start and finish before current time")
hamster_client.start_tracking(activity, start_time, end_time)
elif command == 'stop':
hamster_client.stop_tracking()
elif command == 'list':
start_time, end_time = None, None
if args:
start_time, end_time = parse_datetime_range(args[0])
hamster_client.list(start_time, end_time)
elif command == 'list-activities':
hamster_client.list_activities()
elif command == 'list-categories':
hamster_client.list_categories()
else:
# unknown command - print usage, go home
sys.exit(usage % {'prog': sys.argv[0]})
|