From 15ff3ee51b0d19668450db93467189dd375aa6a1 Mon Sep 17 00:00:00 2001 From: Alasdair Colley Date: Mon, 7 Nov 2011 13:09:52 +0000 Subject: config class created, lunch fixed, two config files now --- perc | 176 +++++++++++++++++++++++++++++++++++++++++---------------------- perc.cfg | 67 ++++++++++++------------ 2 files changed, 149 insertions(+), 94 deletions(-) diff --git a/perc b/perc index 37ed829..a78ab18 100755 --- a/perc +++ b/perc @@ -10,13 +10,11 @@ # doesn't mean you should hit on her and end up creating a paradox that could potentially end the # universe, fucking hell guys this is just for telling percentages of time!) -# TODO 3: Add recognised time periods to be accepted (e.g. Friday, Tomorrow, Next Week, Next Year, New Years, Christmas) -# TODO 4: Display percentage as a chart instead # TODO 5: Extend chart to be different types (bar, pie, raised-arm-man etc.) # Raised arm man chart: \o/ = 1 hour, \ = 20 minutes, \o = 40 minutes etc. # TODO 6: Web trawling important date finder -# TODO 7: Start and end dates to be accepted # TODO 8: Birthdays +# TODO 9: Recurring events # CHANGE LOG: # 3.21: - Bug fix for option --NICK. Changed option back to --nick @@ -35,6 +33,9 @@ # - Ability to add new users added # 3.4.1: # - Can now clone (-c/--clone) existing users to create new users (also allows other options in addition such as -s -f to further customise a new user) +# 3.5: +# - Created Config class to more cleanly get/set config options +# - Split config files into perc.cfg and custom_perc.cfg for options that can change on commit and custom user specified options that are persistent import copy import decimal @@ -48,9 +49,10 @@ import sys from datetime import datetime, date, timedelta, time PROG = "perc" -VERSION = "3.4.1" +VERSION = "3.5" CONFIG_FILE = "perc.cfg" +CUSTOM_CONFIG_FILE = "custom_perc.cfg" NOW = datetime.now() @@ -61,8 +63,8 @@ MODES = ["user", "admin"] USER = None DEFAULT_USER = None -CONFIG = {} -CONFIG_IS_DIRTY = False +CONFIG = None +CUSTOM_CONFIG = None EVENT_DATETIME_FORMAT = "%d-%m-%Y %H:%M" @@ -81,6 +83,7 @@ WEEKEND = [ SATURDAY, SUNDAY ] WORK_WEEK = [ MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY ] IMPORTANT_TEMPORAL_EVENTS = ["LATER", "tomorrow", "friday", "new year", "newyear", "christmas", "xmas", "weekend", "midday", "noon", "lunch", "easter", "halloween"] +RECURRANCE_PERIODS = ["hourly", "daily", "weekly", "fortnightly", "monthly", "yearly"] OUTPUT_TYPES = ["perc", "bar", "ram", "pint"] @@ -180,7 +183,9 @@ def parseTime(timestr): sys.exit(1) def isImportantTemporalEvent(event): - return event in IMPORTANT_TEMPORAL_EVENTS + CONFIG["CUSTOM_EVENTS"].keys() + events = IMPORTANT_TEMPORAL_EVENTS + CUSTOM_CONFIG.get("EVENTS", {}).keys() + + return event in events def getDayOfWeek(day): return (NOW - day_of_week() * ONE_DAY).replace(hour=0, minute=0, second=0, microsecond=0) + day * ONE_DAY @@ -188,10 +193,10 @@ def getDayOfWeek(day): def parseTemporalEvent(event, user): start, finish = None, None - if event in CONFIG["CUSTOM_EVENTS"]: - start = datetime.strptime(CONFIG["CUSTOM_EVENTS"][event]["start"], EVENT_DATETIME_FORMAT) - finish = datetime.strptime(CONFIG["CUSTOM_EVENTS"][event]["finish"], EVENT_DATETIME_FORMAT) - format = CONFIG["CUSTOM_EVENTS"][event]["format"] + if event in CUSTOM_CONFIG["EVENTS"]: + start = datetime.strptime(CUSTOM_CONFIG["EVENTS"][event]["start"], EVENT_DATETIME_FORMAT) + finish = datetime.strptime(CUSTOM_CONFIG["EVENTS"][event]["finish"], EVENT_DATETIME_FORMAT) + format = CUSTOM_CONFIG["EVENTS"][event]["format"] start_formatted = start.strftime(format).format(name=event) finish_formatted = finish.strftime(format).format(name=event) @@ -267,11 +272,6 @@ def parseTemporalEvent(event, user): return start, finish, start_formatted, finish_formatted -def readConfig(): - global CONFIG - with open(CONFIG_FILE, "r") as f: - CONFIG = json.load(f) - def day_of_week(day=date.today()): return date.weekday(day) @@ -293,7 +293,21 @@ def parseDatetime(datetimestring, today_relative=False): time_formats = CONFIG["FORMAT"]["time"].values() combined_formats = combination_pairs(date_formats, time_formats) - for format in date_formats + time_formats + combined_formats: + for format in date_formats: + try: + t = datetime.strptime(datetimestring, format) + return t, format + except ValueError: + pass + + for format in time_formats: + try: + t = datetime.strptime(datetimestring, format) + return datetime.combine(NOW.date(), t.time()), format + except ValueError: + pass + + for format in combined_formats: try: t = datetime.strptime(datetimestring, format) if today_relative: @@ -317,24 +331,14 @@ def addEvent(name, start, finish, format="{name} %Y"): "format": format } - CONFIG["CUSTOM_EVENTS"][name] = event - - if name in CONFIG["CUSTOM_EVENTS"]: - print "Replaced existing event with new info" - else: - print "Event added: '%s'" % name - - global CONFIG_IS_DIRTY - CONFIG_IS_DIRTY = True + if "EVENTS" not in CUSTOM_CONFIG: + CUSTOM_CONFIG["EVENTS"] = {} + + CUSTOM_CONFIG["EVENTS"][name] = event def removeEvent(name): - if name in CONFIG["CUSTOM_EVENTS"]: - del CONFIG["CUSTOM_EVENTS"][name] - - print "Event removed: '%s'" % name - - global CONFIG_IS_DIRTY - CONFIG_IS_DIRTY = True + if "EVENTS" in CUSTOM_CONFIG and name in CUSTOM_CONFIG["EVENTS"]: + del CUSTOM_CONFIG["EVENTS"][name] def parseUserArgs(args, user): start, finish, start_formatted, finish_formatted = user.start, user.finish, user.start.strftime(user.format), user.finish.strftime(user.format) @@ -366,21 +370,6 @@ def parseUserArgs(args, user): return start_formatted, finish_formatted, ratio, output_type -class Event(object): - - Time = property(fget=lambda self: self._time) - Format = property(fget=lambda self: self._format) - - def __init__(self, time, format=None): - self._time = time - self._format = format - - def __str__(self): - return self._time.strftime(self._format) - -class EventImportant(Event): - pass - class Output(object): def perc(self, ratio, start, finish): @@ -470,7 +459,7 @@ class User(object): self.data = data else: try: - self.data = self.__parseData(CONFIG["DEFAULTS"][name]) + self.data = self.__parseData(CONFIG["USERS"][name]) except KeyError: self.data = {} @@ -493,7 +482,7 @@ class User(object): return data def commit(self): - if self.Name in CONFIG["DEFAULTS"] and USER.Name != self.Name: + if self.Name in CONFIG["USERS"] and USER.Name != self.Name: print "Cannot edit someone else's perc settings!" sys.exit(1) @@ -501,12 +490,7 @@ class User(object): print "Cannot edit default user" sys.exit(1) - CONFIG["DEFAULTS"][self.Name] = self.__convertToDict() - - print "User updated in config: '%s'" % self.Name - - global CONFIG_IS_DIRTY - CONFIG_IS_DIRTY = True + CONFIG["USERS"][self.Name] = self.__convertToDict() def __datetimeGetter(self, name): try: @@ -556,6 +540,68 @@ class User(object): def __deepcopy__(self, memo): return User(self.Name, copy.deepcopy(self.data)) +class Config(dict): + + def __init__(self, *args, **kwargs): + if kwargs.get("filename"): + self._filename = kwargs["filename"] + data = Config.__parse(self._filename) + super(Config, self).__init__(data) + else: + super(Config, self).__init__(*args, **kwargs) + Config.__convert(self) + + self.__isdirty = False + + @staticmethod + def __convert(data): + for key, value in data.items(): + if isinstance(value, dict): + data[key] = Config(value) + + return data + + @staticmethod + def __parse(filename): + try: + with open(filename) as f: + return Config.__convert(json.load(f)) + except IOError: + return {} + + def __setitem__(self, key, value): + if isinstance(value, dict): + value = Config(value) + + super(Config, self).__setitem__(key, value) + self.__isdirty = True + + def __delitem__(self, key): + super(Config, self).__delitem__(key) + self.__isdirty = True + + def clear(self): + super(Config, self).clear() + self.__isdirty = True + + def isDirty(self): + if self.__isdirty: + return True + + for key, value in self.items(): + if hasattr(value, "isDirty"): + if value.isDirty(): + return True + + return False + + def save(self): + if self.isDirty(): + with open(self._filename, "w") as f: + json.dump(self, f, indent=4) + + print "%s updated" % self._filename + def admin(args): if USER.Name not in CONFIG["ADMINS"]: print "You do not have admin permissions" @@ -570,6 +616,7 @@ def admin(args): parser.add_option("-c", "--clone", action="store", dest="clone", help="") parser.add_option("-l", "--lunch", action="store_true", default=False, dest="lunch", help="Indicate that lunch times are to be set.") parser.add_option("-r", "--remove-event", action="store", dest="remove_event", help="Remove a named event from %s" % PROG) + parser.add_option("-n", "--recurring", action="store", type="choice", choices=RECURRANCE_PERIODS, dest="recurring", help="Designates that the event to be added is recurring and requires type of recurrance. Choices are: %s" % ", ".join(RECURRANCE_PERIODS)) parser.add_option("-s", "--start", action="store", dest="start", help="Used in conjunction with -a/--add-event option. The start date/time of the event.") parser.add_option("-f", "--finish", action="store", dest="finish", help="Used in conjunction with -a/--add-event option. The finish date/time of the event.") parser.add_option("-p", "--format", action="store", default="", dest="format", help="Used in conjunction with -a/--add-event option. The format of the printed event date/time, uses python strftime formatting and accepts {name} keyword to display name of event.") @@ -634,14 +681,12 @@ def admin(args): if options.remove_event: removeEvent(options.remove_event) - if CONFIG_IS_DIRTY: - with open(CONFIG_FILE, "w") as f: - json.dump(CONFIG, f, indent=4) + saveConfig() def user(args): global options - parser = optparse.OptionParser("{0} [options] | {0} [options]".format(PROG)) + parser = optparse.OptionParser("{0} [options] | {0} [options]. Custom Events: {1}".format(PROG, ", ".join(CUSTOM_CONFIG["EVENTS"].keys() if "EVENTS" in CUSTOM_CONFIG else []))) parser.add_option("-v", "--version", action="store_true", dest="version", help="Display the version of %s" % PROG) parser.add_option("-s", "--start", action="store", dest="start", help="The start time of your particular day") parser.add_option("-f", "--finish", action="store", dest="finish", help="The finish time of your particular day") @@ -671,9 +716,20 @@ def user(args): output = Output() output.output(output_type, ratio, start, finish) +def readConfig(): + global CONFIG, CUSTOM_CONFIG + + CONFIG = Config(filename=CONFIG_FILE) + CUSTOM_CONFIG = Config(filename=CUSTOM_CONFIG_FILE) + +def saveConfig(): + CONFIG.save() + CUSTOM_CONFIG.save() + def main(): readConfig() + # Fix for bb's retardedness args = sys.argv[1:] if "perc" in args: args.remove("perc") diff --git a/perc.cfg b/perc.cfg index f5a9b8c..c5657f8 100644 --- a/perc.cfg +++ b/perc.cfg @@ -1,35 +1,5 @@ { - "FORMAT": { - "date": { - "slashes": "%d/%m/%Y", - "hyphens": "%d-%m-%Y" - }, - "time": { - "12hour": "%I:%M%p", - "military": "%H%M", - "12houronly": "%I%p", - "civilian": "%H:%M" - } - }, - "RATIO_SIGFIGS": 3, - "ADMINS": [ - "bikeman", - "bondroid", - "bottlecap", - "carpnet", - "daveg", - "dhm", - "dsk", - "fbeans", - "sbeans", - "jagw", - "l_bratch", - "miniwork", - "otherlw", - "otherlp", - "wjoe" - ], - "DEFAULTS": { + "USERS": { "daveg": { "start": "07:30", "finish": "18:00", @@ -49,7 +19,7 @@ "finish": "14:00" }, "format": "%I%p" - }, + }, "wjoe": { "start": "09:00", "finish": "17:00", @@ -141,6 +111,36 @@ "format": "%H:%M" } }, + "FORMAT": { + "date": { + "slashes": "%d/%m/%Y", + "hyphens": "%d-%m-%Y" + }, + "time": { + "12hour": "%I:%M%p", + "military": "%H%M", + "12houronly": "%I%p", + "civilian": "%H:%M" + } + }, + "RATIO_SIGFIGS": 3, + "ADMINS": [ + "bikeman", + "bondroid", + "bottlecap", + "carpnet", + "daveg", + "dhm", + "dsk", + "fbeans", + "sbeans", + "jagw", + "l_bratch", + "miniwork", + "otherlw", + "otherlp", + "wjoe" + ], "OUTPUT": { "PERCENTAGE": "{ratio:.2%} {finish} ({start} start)", "BAR": { @@ -148,6 +148,5 @@ "length": 40, "character": "-" } - }, - "CUSTOM_EVENTS": {} + } } -- cgit v1.2.3