diff options
-rw-r--r-- | README.md | 45 | ||||
-rwxr-xr-x | calendar-cli.py | 116 |
2 files changed, 113 insertions, 48 deletions
@@ -20,19 +20,20 @@ Synopsis cli.py should be symlinked to the various commands. -A configuration file (ini-format?) can contain defaults for all the global options. - -The config file may have a section for each CalDAV server ... (todo, think more about this) - ### Global options -(only long options will be available in version 0.1; don't want to pollute the short option space yet) +Only long options will be available up until version 0.10; I don't +want to pollute the short option space before the CLI is reasonably +well-defined. + +Always consult --help for up-to-date and complete listings of options. +The list below will only contain the most important options and may +not be up-to-date and may contain features not implemented yet. * --interactive, -i: stop and query the user rather often -* --caldav-url, --caldav-user, --caldav-pass: how to connect to the CalDAV server -* --caldav-calendar: which calendar to access -* --config: use a specific configuration file (default: $HOME/.calendar-cli.conf) -* --config-section: use a specific section from the config file (i.e. to select a different +* --caldav-url, --caldav-user, --caldav-pass: how to connect to the CalDAV server. Fits better into a configuration file. +* --config-file: use a specific configuration file (default: $HOME/.calendar-cli.conf) +* --config-section: use a specific section from the config file (i.e. to select a different caldav-server to connect to) * --icalendar: instead of connecting to a CalDAV server, write an icalendar file to stdout ### Commands @@ -46,7 +47,7 @@ The config file may have a section for each CalDAV server ... (todo, think more Supported in v0.01: -* dateutil.parser.parse() +* anything recognized by dateutil.parser.parse() All of those would eventually be supported in future versions if it's not too difficult to achieve: @@ -59,11 +60,31 @@ All of those would eventually be supported in future versions if it's not too di Alternatively, endtime or duration can be given through options. +Configuration file +------------------ + +(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: + + { "default": + { "caldav_url": "http://foo.bar.example.com/ical/", + "caldav-user": "luser", + "caldav-pass": "insecure" + } + } + +Optionally, in addition (or even instead) of "default", other "sections" can be created and selected through the --config-section option. The rationale is to allow configuration for multiple CalDAV-servers to remain in the same configuration file. Later versions will eventually be capable of copying events, or putting events into several calendars. + +Remember to `chmod og-r ~/.config/calendar.conf` or `chmod 0600 ~/.config/calendar.conf` + ### Examples Add a calendar item "testevent" at 2013-10-01: - ./calendar-cli.py --caldav-url=http://calendar.bekkenstenveien53c.oslo.no/caldav.php/ --caldav-user=tobias --caldav-pass=banana calendar --calendar-url=http://calendar.bekkenstenveien53c.oslo.no/caldav.php/tobias/calendar/ add 2013-10-01 testevent + ./calendar-cli.py calendar --calendar-url=http://calendar.bekkenstenveien53c.oslo.no/caldav.php/tobias/calendar/ add 2013-10-01 testevent + +(assumes that `caldav-url`, `calldav-pass` and `caldav-user` has been added into configuration file. Those may also be added as command line options) Objectives ---------- @@ -79,8 +100,8 @@ Milestones * CLI-interface for creating ical calendar events (working as of version 0.01) * CalDAV login (working as of version 0.02) * Push calendar item into CalDAV server (working as of version 0.02, but both an URL for the caldav server and an URL for the actual calendar has to be given) +* Config file with CalDAV connection details (working as of version 0.03) * Replace calendar-URL with calendar-path -* Config file with CalDAV connection details * Find default calendar-path * Show agenda * CLI-interface for creating ical todo events diff --git a/calendar-cli.py b/calendar-cli.py index 90727d7..f75bce1 100755 --- a/calendar-cli.py +++ b/calendar-cli.py @@ -11,8 +11,11 @@ from icalendar import Calendar,Event from caldavclientlibrary.client.account import CalDAVAccount from caldavclientlibrary.protocol.url import URL import uuid +import json +import os +import logging -__version__ = "0.02" +__version__ = "0.03" __author__ = "Tobias Brox" __author_short__ = "tobixen" __copyright__ = "Copyright 2013, Tobias Brox" @@ -33,7 +36,7 @@ def caldav_connect(args): return CalDAVAccount(splits.netloc, ssl=ssl, user=args.caldav_user, pswd=args.caldav_pass, root=(splits.path or '/'), principal=None, logging=args.debug_logging) -def calendar_add(args): +def calendar_add(caldav_conn, args): if (not args.calendar_url): ## args.calendar_url must be given ... :/ niy() @@ -61,37 +64,78 @@ def calendar_add(args): elif args.caldav_url: caldav_conn.session.writeData(URL(args.calendar_url+str(uuid.uuid1())+'.ical'), cal.to_ical(), 'text/calendar', method='PUT') -parser = argparse.ArgumentParser() - -## Global options -parser.add_argument("--icalendar", help="Do not connect to CalDAV server, but read/write icalendar format from stdin/stdout", action="store_true") -parser.add_argument("--timezone", help="Timezone to use") -parser.add_argument('--language', help="language used", default="EN") -parser.add_argument("--caldav-url", help="Full URL to the caldav server", metavar="URL") -parser.add_argument("--caldav-user", help="username to log into the caldav server", metavar="USER") -parser.add_argument("--caldav-pass", help="password to log into the caldav server", metavar="PASS") -parser.add_argument("--debug-logging", help="turn on debug logging", action="store_true") - -## TODO: check sys.argv[0] to find command -subparsers = parser.add_subparsers(title='command') - -calendar_parser = subparsers.add_parser('calendar') -calendar_parser.add_argument("--calendar-url", help="URL for calendar to be used") -calendar_subparsers = calendar_parser.add_subparsers(title='subcommand') -calendar_add_parser = calendar_subparsers.add_parser('add') -calendar_add_parser.add_argument('event_time', help="Timestamp and duration of the event. See the documentation for event_time specifications") -calendar_add_parser.add_argument('description', nargs='+') -calendar_add_parser.set_defaults(func=calendar_add) - -calendar_agenda_parser = calendar_subparsers.add_parser('agenda') -calendar_agenda_parser.set_defaults(func=niy) -todo_parser = subparsers.add_parser('todo') -todo_parser.set_defaults(func=niy) -args = parser.parse_args() - -if not args.icalendar: - caldav_conn = caldav_connect(args) - -ret = args.func(args) - - +def main(): + ## This boilerplate pattern is from + ## http://stackoverflow.com/questions/3609852 + ## We want defaults for the command line options to be fetched from the config file + + # Parse any conf_file specification + # We make this parser with add_help=False so that + # it doesn't parse -h and print help. + conf_parser = argparse.ArgumentParser( + description=__doc__, # printed with -h/--help + # Don't mess with format of description + formatter_class=argparse.RawDescriptionHelpFormatter, + # Turn off help, so we print all options in response to -h + add_help=False + ) + conf_parser.add_argument("--config-file", + 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') + args, remaining_argv = conf_parser.parse_known_args() + + config = {} + try: + with open(args.config_file) as config_file: + config = json.load(config_file) + except IOError: + ## File not found + logging.info("no config file found") + except ValueError: + logging.error("error in config file", exc_info=True) + + defaults = config.get('default', {}) + + # Parse rest of arguments + # Don't suppress add_help here so it will handle -h + parser = argparse.ArgumentParser( + # Inherit options from config_parser + parents=[conf_parser] + ) + parser.set_defaults(**defaults) + + ## Global options + parser.add_argument("--icalendar", help="Do not connect to CalDAV server, but read/write icalendar format from stdin/stdout", action="store_true") + parser.add_argument("--timezone", help="Timezone to use") + parser.add_argument('--language', help="language used", default="EN") + parser.add_argument("--caldav-url", help="Full URL to the caldav server", metavar="URL") + parser.add_argument("--caldav-user", help="username to log into the caldav server", metavar="USER") + parser.add_argument("--caldav-pass", help="password to log into the caldav server", metavar="PASS") + parser.add_argument("--debug-logging", help="turn on debug logging", action="store_true") + + ## TODO: check sys.argv[0] to find command + ## TODO: set up logging + subparsers = parser.add_subparsers(title='command') + + calendar_parser = subparsers.add_parser('calendar') + calendar_parser.add_argument("--calendar-url", help="URL for calendar to be used") + calendar_subparsers = calendar_parser.add_subparsers(title='subcommand') + calendar_add_parser = calendar_subparsers.add_parser('add') + calendar_add_parser.add_argument('event_time', help="Timestamp and duration of the event. See the documentation for event_time specifications") + calendar_add_parser.add_argument('description', nargs='+') + calendar_add_parser.set_defaults(func=calendar_add) + + calendar_agenda_parser = calendar_subparsers.add_parser('agenda') + calendar_agenda_parser.set_defaults(func=niy) + todo_parser = subparsers.add_parser('todo') + todo_parser.set_defaults(func=niy) + args = parser.parse_args(remaining_argv) + + if not args.icalendar: + caldav_conn = caldav_connect(args) + + ret = args.func(caldav_conn, args) + +if __name__ == '__main__': + main() |