summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xperc176
-rw-r--r--perc.cfg67
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} <finish> [options] | {0} <start> <finish> [options]".format(PROG))
+ parser = optparse.OptionParser("{0} <finish> [options] | {0} <start> <finish> [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": {}
+ }
}