diff options
author | Tobias Brox <tobias@redpill-linpro.com> | 2015-04-15 21:12:24 +0200 |
---|---|---|
committer | Tobias Brox <tobias@redpill-linpro.com> | 2015-04-15 21:12:24 +0200 |
commit | 84021ac16ba15d2864c91c86bb694877ed8afe43 (patch) | |
tree | 74eafcd9d6785344582474e4e64af2df8aaa7f94 | |
parent | 259e1e2dad457a61e28fa6f756afa5c431b6f1ec (diff) | |
parent | b46e0ce53a872ad68555bf53e830b74f079221a2 (diff) | |
download | calendar-cli-84021ac16ba15d2864c91c86bb694877ed8afe43.zip |
Merge remote-tracking branch 'refs/remotes/origin/master'
-rw-r--r-- | README.md | 47 | ||||
-rwxr-xr-x | calendar-cli.py | 117 | ||||
-rw-r--r-- | setup.py | 2 |
3 files changed, 135 insertions, 31 deletions
@@ -10,14 +10,6 @@ Support \#calendar-cli at irc.freenode.org, eventually t-calendar-cli@tobixen.no, eventually the issue tracker at https://github.com/tobixen/calendar-cli/issues -Status ------- - -This is work in progress. Writing to calendars seems to work, and I'm using it actively for adding stuff to my calendars - taking out the agenda is also possible, but could need more work. - -This is written in python2 as some of the libraries being used aren't -available in python3. - Rationale --------- @@ -63,24 +55,26 @@ not be up-to-date and may contain features not implemented yet. ### Event time specification -Supported in v0.06: +Supported in v0.9: * anything recognized by dateutil.parser.parse() +* An iso time stamp, followed with the duration, using either + or space as separator. Duration is a number postfixed by s for seconds, m for minutes, h for hours, d for days, w for weeks and y for years (i.e. 2013-09-10T13:37+30d) All of those would eventually be supported in future versions if it's not too difficult to achieve: -* An iso time stamp, followed with the duration, using either + or space as separator. Duration is a number postfixed by s for seconds, m for minutes, h for hours, d for days, w for weeks and y for years (i.e. 2013-09-10T13:37+30d) * Two ISO timestamps separated by a dash (-) * ISO dates without times (default duration will be one day, for two dates full days up to and including the end date is counted) * "tomorrow" instead of an ISO date * weekday instead of an ISO date * clock time without the date; event will be assumed to start within 24 hours. -Alternatively, endtime or duration can be given through options (not supported as of 0.06. All events are considered to be one hour long). +Alternatively, endtime or duration can be given through options (not supported as of 0.9) Configuration file ------------------ +Configuration file is by default located in $HOME/.config/calendar.conf and should be in json syntax. As of version 0.8 you may run `calendar-cli --interactive-config` if you don't feel comfortable with hand-crafting configuration in json syntax. + (I considered a configuration file in .ini-format, having a "default"-section with default values for any global options, and optionally other sections for different CalDAV-servers. Asking a bit around for recommendations on config file format as well as location, I was told that the .ini-format is not a standard, I'd be better off using a standard like yaml, json or xml. Personally I like json a bit better than yaml - after consulting with a friend I ended up with json. Location ... I think it's "cleaner" to keep it in ~/.config/, and I'd like any calendar application to be able to access the file, hence it got ~/.config/calendar.conf rather than ~/.calendar-cli.conf) The file may look like this: @@ -114,22 +108,29 @@ Objectives * It should be possible to get out lists ("agenda") of calendar items and todo-items. * Interface for copying calendar items between calendars, even between calendars on distinct caldav servers -Status ------- +Changelog +--------- -2013-09-15: Made a repository at github and wrote up this README. -2013-09-24: version 0.01 - supports creating an ical-file based on command line parameters -2013-09-28: version 0.02 - possible to add a calendar item to the caldav server -2013-10-02: version 0.03 - support for configuration file -2013-10-05: version 0.04 - no need to specify URL for the default calendar -2013-12 - 2014-03: helped cyrilrbt on making a new release of the caldav library -2014-03-07: version 0.05 - rewrote parts of the tool to using the caldav library. Nice!!! -2014-03-14: version 0.6 - now agenda works quite smooth. I think this is becoming a useful tool. -2015-02-15: version 0.7 - supports deletion of events, alternative templates for the event output and a small testing script +* 2013-09-15: Made a repository at github and wrote up this README. +* 2013-09-24: version 0.01 - supports creating an ical-file based on command line parameters +* 2013-09-28: version 0.02 - possible to add a calendar item to the caldav server +* 2013-10-02: version 0.03 - support for configuration file +* 2013-10-05: version 0.04 - no need to specify URL for the default calendar +* 2013-12 - 2014-03: helped cyrilrbt on making a new release of the caldav library +* 2014-03-07: version 0.05 - rewrote parts of the tool to using the caldav library. Nice!!! +* 2014-03-14: version 0.6 - now agenda works quite smooth. I think this is becoming a useful tool. +* 2015-02-15: version 0.7 - supports deletion of events, alternative templates for the event output and a small testing script +* 2015-03-30: version 0.8 - has a interactive configuration mode for those not feeling comfortable with hand-crafting the config in json syntax +* 2015-03-30: version 0.9 - now it's possible to set a duration when adding events to the calendar. Roadmap ------- -* Allow specification of event duration when adding events to calendar +* Allow pulling out an agenda for all calendars at once (though, with the current design it's so much easier to do it through a bash loop rather than in the python code, so this is postponed for a while) +* Allow specification of event duration and/or event end time through options * CLI-interface for creating ical todo events * Fix easy-to-use symlinks (or alternatively wrapper scripts) * Make some nosetests + +Donations +--------- +Donations are not expected, but as long as this is a one-man hobby project at least it's not problematic to receive donations. Send bitcoins to 139xWFKwX9WejtRR1HP917qJGnRkZ6kn4M eventually. Donations may motivate me to work on specific feature requests or issues. diff --git a/calendar-cli.py b/calendar-cli.py index 2f3ece6..ee46699 100755 --- a/calendar-cli.py +++ b/calendar-cli.py @@ -3,9 +3,9 @@ ## (the icalendar library is not ported to python3?) import argparse -import urlparse import pytz import tzlocal +import time from datetime import datetime, timedelta import dateutil.parser from icalendar import Calendar,Event @@ -102,6 +102,78 @@ def calendar_addics(caldav_conn, args): for uid in uids: c.subcomponents = timezones + uids[uid] _calendar_addics(caldav_conn, c.to_ical(), uid, args) + +def interactive_config(args, config, remaining_argv): + import readline + + new_config = False + section = 'default' + backup = {} + modified = False + + print("Welcome to the interactive calendar configuration mode") + print("Warning - untested code ahead, raise issues at t-calendar-cli@tobixen.no") + if not config or not hasattr(config, 'keys'): + config = {} + print("No valid existing configuration found") + new_config = True + if config: + print("The following sections have been found: ") + print("\n".join(config.keys())) + if args.config_section and args.config_section != 'default': + section = args.config_section + else: + ## TODO: tab completion + section = raw_input("Chose one of those, or a new name / no name for a new configuration section: ") + if section in config: + backup = config[section].copy() + print("Using section " + section) + else: + section = 'default' + + if not section in config: + config[section] = {} + + for config_key in ('caldav_url', 'caldav_user', 'caldav_pass', 'language', 'timezone'): + print("Config option %s - old value: %s" % (config_key, config[section].get(config_key, '(None)'))) + value = raw_input("Enter new value (or just enter to keep the old): ") + if value: + config[section][config_key] = value + modified = True + + if not modified: + print("No configuration changes have been done") + else: + options = [] + if section: + options.append(('save', 'save configuration into section %s' % section)) + if backup or not section: + options.append(('save_other', 'add this new configuration into a new section in the configuration file')) + if remaining_argv: + options.append(('use', 'use this configuration without saving')) + options.append(('abort', 'abort without saving')) + print("CONFIGURATION DONE ...") + for o in options: + print("Type %s if you want to %s" % o) + cmd = raw_input("Enter a command: ") + if cmd in ('save', 'save_other'): + if cmd == 'save_other': + new_section = raw_input("New config section name: ") + config[new_section] = config[section] + if backup: + config[section] = backup + else: + del config[section] + section = new_section + if os.path.isfile(args.config_file): + os.rename(args.config_file, "%s.%s.bak" % (args.config_file, int(time.time()))) + with open(args.config_file, 'w') as outfile: + json.dump(config, outfile, indent=4) + + + if args.config_section == 'default' and section != 'default': + config['default'] = config[section] + return config def calendar_add(caldav_conn, args): cal = Calendar() @@ -110,10 +182,31 @@ def calendar_add(caldav_conn, args): event = Event() ## TODO: timezone ## read timestamps from arguments - dtstart = dateutil.parser.parse(args.event_time) + time_units = { + 's': 1, 'm': 60, 'h': 3600, + 'd': 86400, 'w': 604800 + } + event_spec = args.event_time.split('+') + if len(event_spec)>3: + raise ValueError('Invalid event time "%s" - can max contain 2 plus-signs' % event_time) + elif len(event_spec)==3: + event_time = '%s+%s' % tuple(event_spec[0:2]) + event_duration = event_spec[2] + elif len(event_spec)==2 and not event_spec[1][-1:] in time_units: + event_time = '%s+%s' % tuple(event_spec[0:2]) + event_duration = '1h' + elif len(event_spec)==2: + event_time = '%s' % event_spec[0] + event_duration = event_spec[1] + else: + event_time = event_spec[0] + event_duration = '1h' + ## TODO: error handling + event_duration_secs = int(event_duration[:-1]) * time_units[event_duration[-1:]] + dtstart = dateutil.parser.parse(event_spec[0]) event.add('dtstart', dtstart) ## TODO: handle duration and end-time as options. default 3600s by now. - event.add('dtend', dtstart + timedelta(0,3600)) + event.add('dtend', dtstart + timedelta(0,event_duration_secs)) ## not really correct, and it breaks i.e. with google calendar #event.add('dtstamp', datetime.now()) ## maybe we should generate some uid? @@ -171,7 +264,7 @@ def calendar_agenda(caldav_conn, args): events = [] if args.icalendar: for ical in events_: - print ical.data + print(ical.data) else: ## flatten. A recurring event may be a list of events. for event_cal in events_: @@ -215,9 +308,12 @@ def main(): help="Specify config file", metavar="FILE", default=os.getenv('XDG_CONFIG_HOME', os.getenv('HOME', '~') + '/.config')+'/calendar.conf') conf_parser.add_argument("--config-section", help="Specify config section; allows several caldav servers to be configured in the same config file", default='default') + conf_parser.add_argument("--interactive-config", + help="Interactively ask for configuration", action="store_true") args, remaining_argv = conf_parser.parse_known_args() config = {} + try: with open(args.config_file) as config_file: config = json.load(config_file) @@ -225,10 +321,17 @@ def main(): ## File not found logging.info("no config file found") except ValueError: - logging.error("error in config file", exc_info=True) - raise + if args.interactive_config: + logging.error("error in config file. Be aware that the current config file will be ignored and overwritten", exc_info=True) + else: + logging.error("error in config file. You may want to run --interactive-config or fix the config file", exc_info=True) - defaults = config.get(args.config_section, {}) + if args.interactive_config: + config = interactive_config(args, config, remaining_argv) + if not remaining_argv: + return + else: + defaults = config.get(args.config_section, {}) # Parse rest of arguments # Don't suppress add_help here so it will handle -h @@ -36,7 +36,7 @@ setup( scripts=['calendar-cli.py'], install_requires=[ 'icalendar', - 'caldav>=0.2', + 'caldav>=0.2.2', 'pytz', 'tzlocal' ], |