summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md47
-rwxr-xr-xcalendar-cli.py117
-rw-r--r--setup.py2
3 files changed, 135 insertions, 31 deletions
diff --git a/README.md b/README.md
index ab52824..bcb4117 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/setup.py b/setup.py
index ad45ef7..89795e7 100644
--- a/setup.py
+++ b/setup.py
@@ -36,7 +36,7 @@ setup(
scripts=['calendar-cli.py'],
install_requires=[
'icalendar',
- 'caldav>=0.2',
+ 'caldav>=0.2.2',
'pytz',
'tzlocal'
],