Beispiel #1
0
Datei: cli.py Projekt: ploth/khal
def build_collection(ctx):
    try:
        conf = ctx.obj['conf']
        selection = ctx.obj.get('calendar_selection', None)

        props = dict()
        for name, cal in conf['calendars'].items():
            if selection is None or name in ctx.obj['calendar_selection']:
                props[name] = {
                    'name': name,
                    'path': cal['path'],
                    'readonly': cal['readonly'],
                    'color': cal['color'],
                    'ctype': cal['type'],
                }
        collection = khalendar.CalendarCollection(
            calendars=props,
            color=ctx.obj['conf']['highlight_days']['color'],
            locale=ctx.obj['conf']['locale'],
            dbpath=conf['sqlite']['path'],
            hmethod=ctx.obj['conf']['highlight_days']['method'],
            default_color=ctx.obj['conf']['highlight_days']['default_color'],
            multiple=ctx.obj['conf']['highlight_days']['multiple'],
            highlight_event_days=ctx.obj['conf']['default']
            ['highlight_event_days'],
        )
    except FatalError as error:
        logger.fatal(error)
        sys.exit(1)

    collection._default_calendar_name = conf['default']['default_calendar']
    return collection
Beispiel #2
0
Datei: cli.py Projekt: ploth/khal
def build_collection(ctx):
    try:
        conf = ctx.obj['conf']
        selection = ctx.obj.get('calendar_selection', None)

        props = dict()
        for name, cal in conf['calendars'].items():
            if selection is None or name in ctx.obj['calendar_selection']:
                props[name] = {
                    'name': name,
                    'path': cal['path'],
                    'readonly': cal['readonly'],
                    'color': cal['color'],
                    'ctype': cal['type'],
                }
        collection = khalendar.CalendarCollection(
            calendars=props,
            color=ctx.obj['conf']['highlight_days']['color'],
            locale=ctx.obj['conf']['locale'],
            dbpath=conf['sqlite']['path'],
            hmethod=ctx.obj['conf']['highlight_days']['method'],
            default_color=ctx.obj['conf']['highlight_days']['default_color'],
            multiple=ctx.obj['conf']['highlight_days']['multiple'],
            highlight_event_days=ctx.obj['conf']['default']['highlight_event_days'],
        )
    except FatalError as error:
        logger.fatal(error)
        sys.exit(1)

    collection._default_calendar_name = conf['default']['default_calendar']
    return collection
Beispiel #3
0
def build_collection(ctx):
    try:
        conf = ctx.obj['conf']
        collection = khalendar.CalendarCollection(
            hmethod=ctx.obj['conf']['highlight_days']['method'],
            default_color=ctx.obj['conf']['highlight_days']['default_color'],
            multiple=ctx.obj['conf']['highlight_days']['multiple'],
            color=ctx.obj['conf']['highlight_days']['color'],
            highlight_event_days=ctx.obj['conf']['default']['highlight_event_days'],
            locale=ctx.obj['conf']['locale'])
        selection = ctx.obj.get('calendar_selection', None)

        for name, cal in conf['calendars'].items():
            if selection is None or name in ctx.obj['calendar_selection']:
                collection.append(khalendar.Calendar(
                    name=name,
                    dbpath=conf['sqlite']['path'],
                    path=cal['path'],
                    readonly=cal['readonly'],
                    color=cal['color'],
                    unicode_symbols=conf['locale']['unicode_symbols'],
                    locale=conf['locale'],
                    ctype=cal['type'],
                ))
    except FatalError as error:
        logger.fatal(error)
        sys.exit(1)

    collection._default_calendar_name = conf['default']['default_calendar']
    return collection
Beispiel #4
0
    def __init__(self, collection, conf, date_list, location=None, repeat=None):
        try:
            event = aux.construct_event(
                date_list,
                location=location,
                repeat=repeat,
                **conf['locale'])
        except FatalError:
            sys.exit(1)
        event = Event(event,
                      collection.default_calendar_name,
                      locale=conf['locale'],
                      )

        try:
            collection.new(event)
        except ReadOnlyCalendarError:
            logger.fatal('ERROR: Cannot modify calendar "{}" as it is '
                         'read-only'.format(collection.default_calendar_name))
            sys.exit(1)
        if conf['default']['print_new'] == 'event':
            echo(event.long())
        elif conf['default']['print_new'] == 'path':
            path = collection._calnames[event.calendar].path + event.href
            echo(path.encode(conf['locale']['encoding']))
Beispiel #5
0
def new_from_args(collection, calendar_name, conf, dtstart=None, dtend=None,
                  summary=None, description=None, allday=None, location=None,
                  categories=None, repeat=None, until=None, alarms=None,
                  timezone=None, format=None, env=None):

    try:
        event = aux.new_event(locale=conf['locale'], location=location,
                              categories=categories, repeat=repeat, until=until,
                              alarms=alarms, dtstart=dtstart, dtend=dtend,
                              summary=summary, description=description,
                              timezone=timezone)
    except ValueError as e:
        logger.fatal('ERROR: '+str(e))
        sys.exit(1)
    except FatalError:
        sys.exit(1)
    event = Event.fromVEvents(
        [event], calendar=calendar_name, locale=conf['locale'])

    try:
        collection.new(event)
    except ReadOnlyCalendarError:
        logger.fatal('ERROR: Cannot modify calendar "{}" as it is '
                     'read-only'.format(calendar_name))
        sys.exit(1)

    if conf['default']['print_new'] == 'event':
        if format is None:
            format = conf['view']['event_format']
        echo(event.format(format, datetime.now(), env=env))
    elif conf['default']['print_new'] == 'path':
        path = collection._calnames[event.calendar].path + event.href
        echo(path)
    return event
Beispiel #6
0
def get_config(config_path=None):
    """reads the config file, validates it and return a config dict

    :param config_path: path to a custom config file, if none is given the
                        default locations will be searched
    :type config_path: str
    :returns: configuration
    :rtype: dict
    """
    if config_path is None:
        config_path = _find_configuration_file()

    logger.debug('using the config file at {}'.format(config_path))
    config = ConfigObj(DEFAULTSPATH, interpolation=False)

    try:
        user_config = ConfigObj(config_path,
                                configspec=SPECPATH,
                                interpolation=False,
                                file_error=True,
                                )
    except ConfigObjError as error:
        logger.fatal('parsing the config file file with the following error: '
                     '{}'.format(error))
        logger.fatal('if you recently updated khal, the config file format '
                     'might have changed, in that case please consult the '
                     'CHANGELOG or other documentation')
        sys.exit(1)

    fdict = {'timezone': is_timezone,
             'expand_path': expand_path,
             }
    validator = Validator(fdict)
    results = user_config.validate(validator, preserve_errors=True)
    if not results:
        for entry in flatten_errors(config, results):
            # each entry is a tuple
            section_list, key, error = entry
            if key is not None:
                section_list.append(key)
            else:
                section_list.append('[missing section]')
            section_string = ', '.join(section_list)
            if error is False:
                error = 'Missing value or section.'
            print(section_string, ' = ', error)
        raise ValueError  # TODO own error class

    config.merge(user_config)
    config_checks(config)

    extras = get_extra_values(user_config)
    for section, value in extras:
        if section == ():
            logger.warn('unknown section "{}" in config file'.format(value))
        else:
            section = sectionize(section)
            logger.warn('unknown key or subsection "{}" in '
                        'section "{}"'.format(value, section))
    return config
Beispiel #7
0
def new_from_string(collection, calendar_name, conf, date_list, location=None, repeat=None,
                    until=None):
    """construct a new event from a string and add it"""
    try:
        event = aux.construct_event(
            date_list,
            location=location,
            repeat=repeat,
            until=until,
            locale=conf['locale'])
    except FatalError:
        sys.exit(1)
    event = Event.fromVEvents(
        [event], calendar=calendar_name, locale=conf['locale'])

    try:
        collection.new(event)
    except ReadOnlyCalendarError:
        logger.fatal('ERROR: Cannot modify calendar "{}" as it is '
                     'read-only'.format(calendar_name))
        sys.exit(1)
    if conf['default']['print_new'] == 'event':
        echo(event.event_description)
    elif conf['default']['print_new'] == 'path':
        path = collection._calnames[event.calendar].path + event.href
        echo(path.encode(conf['locale']['encoding']))
Beispiel #8
0
    def __init__(self, collection, conf, date_list, location=None, repeat=None):
        try:
            event = aux.construct_event(
                date_list,
                location=location,
                repeat=repeat,
                **conf['locale'])
        except FatalError:
            sys.exit(1)
        event = Event(event,
                      collection.default_calendar_name,
                      locale=conf['locale'],
                      )

        try:
            collection.new(event)
        except ReadOnlyCalendarError:
            logger.fatal('ERROR: Cannot modify calendar "{}" as it is '
                         'read-only'.format(collection.default_calendar_name))
            sys.exit(1)
        if conf['default']['print_new'] == 'event':
            print(event.long())
        elif conf['default']['print_new'] == 'path':
            path = collection._calnames[event.calendar].path + event.href
            print(path.encode(conf['locale']['encoding']))
Beispiel #9
0
def rrulefstr(repeat, until, locale):
    if repeat in ["daily", "weekly", "monthly", "yearly"]:
        rrule_settings = {'freq': repeat}
        if until:
            until_date = None
            for fun, dformat in [(datetimefstr, locale['datetimeformat']),
                                 (datetimefstr, locale['longdatetimeformat']),
                                 (timefstr, locale['timeformat']),
                                 (datetimefstr, locale['dateformat']),
                                 (datetimefstr, locale['longdateformat'])]:
                try:
                    until_date = fun(until.split(' '), dformat)
                    break
                except ValueError:
                    pass
            if until_date is None:
                logger.fatal("Cannot parse until date: '{}'\nPlease have a look "
                             "at the documentation.".format(until))
                raise FatalError()
            rrule_settings['until'] = until_date

        return rrule_settings
    else:
        logger.fatal("Invalid value for the repeat option. \
                Possible values are: daily, weekly, monthly or yearly")
        raise FatalError()
Beispiel #10
0
def new_from_string(collection, calendar_name, conf, date_list, location=None, repeat=None,
                    until=None):
    """construct a new event from a string and add it"""
    try:
        event = aux.construct_event(
            date_list,
            location=location,
            repeat=repeat,
            until=until,
            locale=conf['locale'])
    except FatalError:
        sys.exit(1)
    event = Event.fromVEvents(
        [event], calendar=calendar_name, locale=conf['locale'])

    try:
        collection.new(event)
    except ReadOnlyCalendarError:
        logger.fatal('ERROR: Cannot modify calendar "{}" as it is '
                     'read-only'.format(calendar_name))
        sys.exit(1)
    if conf['default']['print_new'] == 'event':
        echo(event.event_description)
    elif conf['default']['print_new'] == 'path':
        path = collection._calnames[event.calendar].path + event.href
        echo(path.encode(conf['locale']['encoding']))
Beispiel #11
0
def rrulefstr(repeat, until, locale):
    if repeat in ["daily", "weekly", "monthly", "yearly"]:
        rrule_settings = {'freq': repeat}
        if until:
            until_date = None
            for fun, dformat in [(datetimefstr, locale['datetimeformat']),
                                 (datetimefstr, locale['longdatetimeformat']),
                                 (timefstr, locale['timeformat']),
                                 (datetimefstr, locale['dateformat']),
                                 (datetimefstr, locale['longdateformat'])]:
                try:
                    until_date = fun(until.split(' '), dformat)
                    break
                except ValueError:
                    pass
            if until_date is None:
                logger.fatal("Cannot parse until date: '{}'\nPlease have a look "
                             "at the documentation.".format(until))
                raise FatalError()
            rrule_settings['until'] = until_date

        return rrule_settings
    else:
        logger.fatal("Invalid value for the repeat option. \
                Possible values are: daily, weekly, monthly or yearly")
        raise FatalError()
Beispiel #12
0
def new_from_args(collection,
                  calendar_name,
                  conf,
                  dtstart=None,
                  dtend=None,
                  summary=None,
                  description=None,
                  allday=None,
                  location=None,
                  categories=None,
                  repeat=None,
                  until=None,
                  alarms=None,
                  timezone=None,
                  format=None,
                  env=None):

    try:
        event = utils.new_event(
            locale=conf['locale'],
            location=location,
            categories=categories,
            repeat=repeat,
            until=until,
            alarms=alarms,
            dtstart=dtstart,
            dtend=dtend,
            summary=summary,
            description=description,
            timezone=timezone,
        )
    except ValueError as e:
        logger.fatal('ERROR: ' + str(e))
        sys.exit(1)
    except FatalError:
        sys.exit(1)
    event = Event.fromVEvents([event],
                              calendar=calendar_name,
                              locale=conf['locale'])

    try:
        collection.new(event)
    except ReadOnlyCalendarError:
        logger.fatal(
            'ERROR: Cannot modify calendar "{}" as it is read-only'.format(
                calendar_name))
        sys.exit(1)

    if conf['default']['print_new'] == 'event':
        if format is None:
            format = conf['view']['event_format']
        echo(event.format(format, datetime.now(), env=env))
    elif conf['default']['print_new'] == 'path':
        path = collection._calnames[event.calendar].path + event.href
        echo(path)
    return event
Beispiel #13
0
def configwizard(dry_run=False):
    config_file = settings.find_configuration_file()
    if not dry_run and config_file is not None:
        logger.fatal("Found an existing config file at {}.".format(config_file))
        logger.fatal(
            "If you want to create a new configuration file, "
            "please remove the old one first. Exiting.")
        sys.exit(1)
    dateformat = choose_datetime_format()
    print()
    timeformat = choose_time_format()
    print()
    vdirs = get_vdirs_from_vdirsyncer_config()
    print()
    if not vdirs:
        vdirs = create_vdir()
        print()

    calendars = '[calendars]\n'
    for name, path, type_ in vdirs:
        calendars += '''[[{name}]]
path = {path}
type = {type}
'''.format(name=name, path=path, type=type_)
    locale = '''[locale]
timeformat = {timeformat}
dateformat = {dateformat}
longdateformat = {longdateformat}
datetimeformat = {dateformat} {timeformat}
longdatetimeformat = {longdateformat} {timeformat}
'''.format(
        timeformat=timeformat,
        dateformat=dateformat,
        longdateformat=dateformat,
    )
    config = '\n'.join([calendars, locale])
    config_path = join(xdg.BaseDirectory.xdg_config_home, 'khal', 'khal.conf')

    if not confirm(
            "Do you want to write the config to {}? "
            "(Choosing `No` will abort)".format(config_path)):
        print('Aborting...')
        sys.exit(1)
    if dry_run:
        print(config)
        sys.exit(0)
    config_dir = join(xdg.BaseDirectory.xdg_config_home, 'khal')
    if not exists(config_dir) and not isdir(config_dir):
        makedirs(config_dir)
        print('created directory {}'.format(config_dir))
    with open(config_path, 'w') as config_file:
        config_file.write(config)
    print("Successfully wrote configuration to {}".format(config_path))
Beispiel #14
0
def validate(conf, logger):
    """
    validate the config
    """
    rval = True
    cal_name = conf.default.default_calendar
    if cal_name is True:
        conf.default.default_calendar = conf.calendars[0].name

    else:
        if cal_name not in [cal.name for cal in conf.calendars]:
            logger.fatal('{} is not a valid calendar'.format(cal_name))
            rval = False
    if rval:
        return conf
    else:
        return None
Beispiel #15
0
def validate(conf, logger):
    """
    validate the config
    """
    rval = True
    cal_name = conf.default.default_calendar
    if cal_name is True:
        conf.default.default_calendar = conf.calendars[0].name

    else:
        if cal_name not in [cal.name for cal in conf.calendars]:
            logger.fatal('{} is not a valid calendar'.format(cal_name))
            rval = False
    if rval:
        return conf
    else:
        return None
Beispiel #16
0
def configwizard():
    config_file = settings.find_configuration_file()
    if config_file is not None:
        logger.fatal(
            "Found an existing config file at {}.".format(config_file))
        logger.fatal("If you want to create a new configuration file, "
                     "please remove the old one first. Exiting.")
        raise FatalError()
    dateformat = choose_datetime_format()
    print()
    timeformat = choose_time_format()
    print()
    vdirs = get_vdirs_from_vdirsyncer_config()
    print()
    if not vdirs:
        try:
            vdirs = create_vdir()
        except OSError as error:
            raise FatalError(error)

    if not vdirs:
        print(
            "\nWARNING: no vdir configured, khal will not be usable like this!\n"
        )

    config = create_config(vdirs, dateformat=dateformat, timeformat=timeformat)
    config_path = join(xdg.BaseDirectory.xdg_config_home, 'khal', 'config')
    if not confirm("Do you want to write the config to {}? "
                   "(Choosing `No` will abort)".format(config_path),
                   default=True):
        raise FatalError('User aborted...')
    config_dir = join(xdg.BaseDirectory.xdg_config_home, 'khal')
    if not exists(config_dir) and not isdir(config_dir):
        try:
            makedirs(config_dir)
        except OSError as error:
            print("Could not write config file at {} because of {}. "
                  "Aborting".format(config_dir, error))
            raise FatalError(error)
        else:
            print('created directory {}'.format(config_dir))
    with open(config_path, 'w') as config_file:
        config_file.write(config)
    print("Successfully wrote configuration to {}".format(config_path))
Beispiel #17
0
Datei: cli.py Projekt: ploth/khal
    def at(ctx, datetime=None):
        '''Show all events scheduled for DATETIME.

        if DATETIME is given (or the string `now`) all events scheduled
        for this moment are shown, if only a time is given, the date is assumed
        to be today
        '''
        collection = build_collection(ctx)
        locale = ctx.obj['conf']['locale']
        dtime_list = list(datetime)
        if dtime_list == [] or dtime_list == ['now']:
            import datetime
            dtime = datetime.datetime.now()
        else:
            try:
                dtime, _ = aux.guessdatetimefstr(dtime_list, locale)
            except ValueError:
                logger.fatal(
                    '{} is not a valid datetime (matches neither {} nor {} nor'
                    ' {})'.format(
                        ' '.join(dtime_list),
                        locale['timeformat'],
                        locale['datetimeformat'],
                        locale['longdatetimeformat']))
                sys.exit(1)
        dtime = locale['local_timezone'].localize(dtime)
        dtime = dtime.astimezone(pytz.UTC)
        events = collection.get_events_at(dtime)
        event_column = list()
        term_width, _ = get_terminal_size()
        for event in events:
            lines = list()
            items = event.event_description.splitlines()
            for item in items:
                lines += textwrap.wrap(item, term_width)
            event_column.extend(
                [colored(line, event.color,
                         bold_for_light_color=ctx.obj['conf']['view']['bold_for_light_color'])
                 for line in lines]
            )
        click.echo(
            '\n'.join(event_column).encode(ctx.obj['conf']['locale']['encoding'])
        )
Beispiel #18
0
    def __init__(self, collection, conf, date_list):
        try:
            event = aux.construct_event(date_list, **conf["locale"])
        except FatalError:
            sys.exit(1)
        event = Event(
            event,
            collection.default_calendar_name,
            local_tz=conf["locale"]["local_timezone"],
            default_tz=conf["locale"]["default_timezone"],
        )

        try:
            collection.new(event)
        except ReadOnlyCalendarError:
            logger.fatal(
                'ERROR: Cannot modify calendar "{}" as it is ' "read-only".format(collection.default_calendar_name)
            )
            sys.exit(1)
Beispiel #19
0
def configwizard():
    config_file = settings.find_configuration_file()
    if config_file is not None:
        logger.fatal("Found an existing config file at {}.".format(config_file))
        logger.fatal(
            "If you want to create a new configuration file, "
            "please remove the old one first. Exiting.")
        sys.exit(1)
    dateformat = choose_datetime_format()
    print()
    timeformat = choose_time_format()
    print()
    vdirs = get_vdirs_from_vdirsyncer_config()
    print()
    if not vdirs:
        try:
            vdirs = [create_vdir()]
        except OSError as error:
            sys.exit(1)

    config = create_config(vdirs, dateformat=dateformat, timeformat=timeformat)
    config_path = join(xdg.BaseDirectory.xdg_config_home, 'khal', 'config')
    if not confirm(
            "Do you want to write the config to {}? "
            "(Choosing `No` will abort)".format(config_path)):
        print('Aborting...')
        sys.exit(1)
    config_dir = join(xdg.BaseDirectory.xdg_config_home, 'khal')
    if not exists(config_dir) and not isdir(config_dir):
        try:
            makedirs(config_dir)
        except OSError as error:
            print(
                "Could not write config file at {} because of {}. "
                "Aborting".format(config_dir, error)
            )
            sys.exit(1)
        else:
            print('created directory {}'.format(config_dir))
    with open(config_path, 'w') as config_file:
        config_file.write(config)
    print("Successfully wrote configuration to {}".format(config_path))
Beispiel #20
0
    def __init__(self, collection, conf, date_list, location=None):
        try:
            event = aux.construct_event(
                date_list,
                location=location,
                **conf['locale'])
        except FatalError:
            sys.exit(1)
        event = Event(event,
                      collection.default_calendar_name,
                      local_tz=conf['locale']['local_timezone'],
                      default_tz=conf['locale']['default_timezone'],
                      )

        try:
            collection.new(event)
        except ReadOnlyCalendarError:
            logger.fatal('ERROR: Cannot modify calendar "{}" as it is '
                         'read-only'.format(collection.default_calendar_name))
            sys.exit(1)
Beispiel #21
0
Datei: cli.py Projekt: ploth/khal
    def at(ctx, datetime=None):
        '''Show all events scheduled for DATETIME.

        if DATETIME is given (or the string `now`) all events scheduled
        for this moment are shown, if only a time is given, the date is assumed
        to be today
        '''
        collection = build_collection(ctx)
        locale = ctx.obj['conf']['locale']
        dtime_list = list(datetime)
        if dtime_list == [] or dtime_list == ['now']:
            import datetime
            dtime = datetime.datetime.now()
        else:
            try:
                dtime, _ = aux.guessdatetimefstr(dtime_list, locale)
            except ValueError:
                logger.fatal(
                    '{} is not a valid datetime (matches neither {} nor {} nor'
                    ' {})'.format(' '.join(dtime_list), locale['timeformat'],
                                  locale['datetimeformat'],
                                  locale['longdatetimeformat']))
                sys.exit(1)
        dtime = locale['local_timezone'].localize(dtime)
        dtime = dtime.astimezone(pytz.UTC)
        events = collection.get_events_at(dtime)
        event_column = list()
        term_width, _ = get_terminal_size()
        for event in events:
            lines = list()
            items = event.event_description.splitlines()
            for item in items:
                lines += textwrap.wrap(item, term_width)
            event_column.extend([
                colored(line,
                        event.color,
                        bold_for_light_color=ctx.obj['conf']['view']
                        ['bold_for_light_color']) for line in lines
            ])
        click.echo('\n'.join(event_column).encode(
            ctx.obj['conf']['locale']['encoding']))
Beispiel #22
0
def build_collection(ctx):
    try:
        conf = ctx.obj['conf']
        collection = khalendar.CalendarCollection()
        selection = ctx.obj.get('calendar_selection', None)

        for name, cal in conf['calendars'].items():
            if selection is None or name in ctx.obj['calendar_selection']:
                collection.append(khalendar.Calendar(
                    name=name,
                    dbpath=conf['sqlite']['path'],
                    path=cal['path'],
                    readonly=cal['readonly'],
                    color=cal['color'],
                    unicode_symbols=conf['locale']['unicode_symbols'],
                    locale=conf['locale'],
                    ctype=cal['type'],
                ))
    except FatalError as error:
        logger.fatal(error)
        sys.exit(1)

    collection._default_calendar_name = conf['default']['default_calendar']
    return collection
Beispiel #23
0
def construct_event(date_list, timeformat, dateformat, longdateformat,
                    datetimeformat, longdatetimeformat, default_timezone,
                    defaulttimelen=60, defaultdatelen=1, encoding='utf-8',
                    description=None, location=None, repeat=None, until = None,
                    _now=datetime.now, **kwargs):
    """takes a list of strings and constructs a vevent from it

    :param encoding: the encoding of your terminal, should be a valid encoding
    :type encoding: str
    :param _now: function that returns now, used for testing

    the parts of the list can be either of these:
        * datetime datetime description
            start and end datetime specified, if no year is given, this year
            is used, if the second datetime has no year, the same year as for
            the first datetime object will be used, unless that would make
            the event end before it begins, in which case the next year is
            used
        * datetime time description
            end date will be same as start date, unless that would make the
            event end before it has started, then the next day is used as
            end date
        * datetime description
            event will last for defaulttime
        * time time description
            event starting today at the first time and ending today at the
            second time, unless that would make the event end before it has
            started, then the next day is used as end date
        * time description
            event starting today at time, lasting for the default length
        * date date description
            all day event starting on the first and ending on the last event
        * date description
            all day event starting at given date and lasting for default length

    datetime should match datetimeformat or longdatetimeformat
    time should match timeformat

    where description is the unused part of the list
    see tests for examples

    """
    today = datetime.today()

    all_day = False

    # looking for start datetime
    try:
        # first two elements are a date and a time
        dtstart = datetimefstr(date_list, datetimeformat, longdatetimeformat)
    except ValueError:
        try:
            # first element is a time
            dtstart = timefstr(date_list, timeformat)
        except ValueError:
            try:
                # first element is a date (and since second isn't a time this
                # is an all-day-event
                dtstart = datetimefstr(date_list, dateformat, longdateformat)
                all_day = True
            except ValueError:
                logger.fatal("Cannot parse: '{}'\nPlease have a look at "
                             "the documentation.".format(' '.join(date_list)))
                raise FatalError()

    # now looking for the end
    if all_day:
        try:
            # second element must be a date, too
            dtend = datetimefstr(date_list, dateformat, longdateformat)
            dtend = dtend + timedelta(days=1)
        except ValueError:
            # if it isn't we expect it to be the summary and use defaultdatelen
            # as event length
            dtend = dtstart + timedelta(days=defaultdatelen)
        # test if dtend's year is this year, but dtstart's year is not
        if dtend.year == today.year and dtstart.year != today.year:
            dtend = datetime(dtstart.year, *dtend.timetuple()[1:6])

        if dtend < dtstart:
            dtend = datetime(dtend.year + 1, *dtend.timetuple()[1:6])

    else:
        try:
            # next element datetime
            dtend = datetimefstr(date_list, datetimeformat, longdatetimeformat,
                                 dtstart.year)
        except ValueError:
            try:
                # next element time only
                dtend = timefstr(date_list, timeformat)
                dtend = datetime(
                    *(dtstart.timetuple()[:3] + dtend.timetuple()[3:5]))
            except ValueError:
                dtend = dtstart + timedelta(minutes=defaulttimelen)

    if dtend < dtstart:
        dtend = datetime(*dtstart.timetuple()[0:3] +
                         dtend.timetuple()[3:5])
    if dtend < dtstart:
        dtend = dtend + timedelta(days=1)
    if all_day:
        dtstart = dtstart.date()
        dtend = dtend.date()

    else:
        try:
            # next element is a valid Olson db timezone string
            dtstart = pytz.timezone(date_list[0]).localize(dtstart)
            dtend = pytz.timezone(date_list[0]).localize(dtend)
            date_list.pop(0)
        except (pytz.UnknownTimeZoneError, UnicodeDecodeError):
            dtstart = default_timezone.localize(dtstart)
            dtend = default_timezone.localize(dtend)

    event = icalendar.Event()
    text = to_unicode(' '.join(date_list), encoding)
    if not description or not location:
        summary = text.split(' :: ', 1)[0]
        try:
            description = text.split(' :: ', 1)[1]
        except IndexError:
            pass
    else:
        summary = text

    if description:
        event.add('description', description)
    if location:
        event.add('location', location)
    if repeat and repeat != "none":
        if repeat in ["daily", "weekly", "monthly", "yearly"]:
            rrule_settings = {'freq': repeat}
            if until :
                try:
                    # first two elements are a date and a time
                    until_date = datetimefstr(until, datetimeformat, longdatetimeformat)
                except ValueError:
                    try:
                        # first element is a time
                        until_date = timefstr(until, timeformat)
                    except ValueError:
                        try:
                            # first element is a date (and since second isn't a time this
                            # is an all-day-event
                            until_date = datetimefstr(until, dateformat, longdateformat)
                        except ValueError:
                            logger.fatal("Cannot parse until date: '{}'\nPlease have a look at "
                                            "the documentation.".format(until))
                            raise FatalError()
                rrule_settings['until'] = until_date

            event.add('rrule', rrule_settings)
        else:
            logger.fatal("Invalid value for the repeat option. \
                    Possible values are: daily, weekly, monthly or yearly")
            raise FatalError()

    event.add('dtstart', dtstart)
    event.add('dtend', dtend)
    event.add('dtstamp', _now())
    event.add('summary', summary)
    event.add('uid', generate_random_uid())  # TODO add proper UID
    return event
Beispiel #24
0
def guessrangefstr(
        daterange,
        locale,
        adjust_reasonably=False,
        default_timedelta_date=timedelta(days=1),
        default_timedelta_datetime=timedelta(hours=1),
):
    """parses a range string

    :param daterange: date1 [date2 | timedelta]
    :type daterange: str or list
    :param locale:
    :returns: start and end of the date(time) range  and if
        this is an all-day time range or not,
        **NOTE**: the end is *exclusive* if this is an allday event
    :rtype: (datetime, datetime, bool)

    """
    range_list = daterange
    if isinstance(daterange, str):
        range_list = daterange.split(' ')

    if range_list == ['week']:
        today_weekday = datetime.today().weekday()
        start = datetime.today() - timedelta(days=(today_weekday -
                                                   locale['firstweekday']))
        end = start + timedelta(days=8)
        return start, end, True

    for i in reversed(range(1, len(range_list) + 1)):
        start = ' '.join(range_list[:i])
        end = ' '.join(range_list[i:])
        allday = False
        try:
            # figuring out start
            if len(start) == 0:
                raise  # used to be: start = datetime_fillin(end=False)
            else:
                split = start.split(" ")
                start, allday = guessdatetimefstr(split, locale)
                if len(split) != 0:
                    continue

            # and end
            if len(end) == 0:
                if allday:
                    end = start + default_timedelta_date
                else:
                    end = start + default_timedelta_datetime
            elif end.lower() == 'eod':
                end = datetime.combine(start.date(), time.max)
            elif end.lower() == 'week':
                start -= timedelta(days=(start.weekday() -
                                         locale['firstweekday']))
                end = start + timedelta(days=8)
            else:
                try:
                    delta = guesstimedeltafstr(end)
                    if allday and delta.total_seconds() % (3600 * 24):
                        # TODO better error class, no logging in here
                        logger.fatal(
                            "Cannot give delta containing anything but whole days for allday events"
                        )
                        raise FatalError()
                    elif delta.total_seconds() == 0:
                        logger.fatal(
                            "Events that last no time are not allowed")
                        raise FatalError()

                    end = start + delta
                except ValueError:
                    split = end.split(" ")
                    end, end_allday = guessdatetimefstr(
                        split, locale, default_day=start.date())
                    if len(split) != 0:
                        continue
                    if allday:
                        end += timedelta(days=1)

            if adjust_reasonably:
                if allday:
                    # test if end's year is this year, but start's year is not
                    today = datetime.today()
                    if end.year == today.year and start.year != today.year:
                        end = datetime(start.year, *end.timetuple()[1:6])

                    if end < start:
                        end = datetime(end.year + 1, *end.timetuple()[1:6])

                if end < start:
                    end = datetime(*start.timetuple()[0:3] +
                                   end.timetuple()[3:5])
                if end < start:
                    end = end + timedelta(days=1)
            return start, end, allday
        except ValueError:
            pass

    raise ValueError('Could not parse `{}` as a daterange'.format(daterange))
Beispiel #25
0
def construct_event(dtime_list, locale,
                    defaulttimelen=60, defaultdatelen=1, encoding='utf-8',
                    description=None, location=None, repeat=None, until=None,
                    _now=datetime.now, **kwargs):
    """takes a list of strings and constructs a vevent from it

    :param encoding: the encoding of your terminal, should be a valid encoding
    :type encoding: str
    :param _now: function that returns now, used for testing

    the parts of the list can be either of these:
        * datetime datetime description
            start and end datetime specified, if no year is given, this year
            is used, if the second datetime has no year, the same year as for
            the first datetime object will be used, unless that would make
            the event end before it begins, in which case the next year is
            used
        * datetime time description
            end date will be same as start date, unless that would make the
            event end before it has started, then the next day is used as
            end date
        * datetime description
            event will last for defaulttime
        * time time description
            event starting today at the first time and ending today at the
            second time, unless that would make the event end before it has
            started, then the next day is used as end date
        * time description
            event starting today at time, lasting for the default length
        * date date description
            all day event starting on the first and ending on the last event
        * date description
            all day event starting at given date and lasting for default length

    datetime should match datetimeformat or longdatetimeformat
    time should match timeformat

    where description is the unused part of the list
    see tests for examples

    """
    # TODO remove if this survives for some time in the wild without getting any reports
    first_type = type(dtime_list[0])
    try:
        for part in dtime_list:
            assert first_type == type(part)
    except AssertionError:
        logger.error(
            "An internal error occured, please report the below error message "
            "to khal's developers at https://github.com/geier/khal/issues or "
            "via email at [email protected]")
        logger.error(' '.join(['{} ({})'.format(part, type(part)) for part in dtime_list]))

    today = datetime.today()
    try:
        dtstart, all_day = guessdatetimefstr(dtime_list, locale)
    except ValueError:
        logger.fatal("Cannot parse: '{}'\nPlease have a look at "
                     "the documentation.".format(' '.join(dtime_list)))
        raise FatalError()

    try:
        dtend, _ = guessdatetimefstr(dtime_list, locale, dtstart)
    except ValueError:
        if all_day:
            dtend = dtstart + timedelta(days=defaultdatelen - 1)
        else:
            dtend = dtstart + timedelta(minutes=defaulttimelen)

    if all_day:
        dtend += timedelta(days=1)
        # test if dtend's year is this year, but dtstart's year is not
        if dtend.year == today.year and dtstart.year != today.year:
            dtend = datetime(dtstart.year, *dtend.timetuple()[1:6])

        if dtend < dtstart:
            dtend = datetime(dtend.year + 1, *dtend.timetuple()[1:6])

    if dtend < dtstart:
        dtend = datetime(*dtstart.timetuple()[0:3] +
                         dtend.timetuple()[3:5])
    if dtend < dtstart:
        dtend = dtend + timedelta(days=1)
    if all_day:
        dtstart = dtstart.date()
        dtend = dtend.date()

    else:
        try:
            # next element is a valid Olson db timezone string
            dtstart = pytz.timezone(dtime_list[0]).localize(dtstart)
            dtend = pytz.timezone(dtime_list[0]).localize(dtend)
            dtime_list.pop(0)
        except (pytz.UnknownTimeZoneError, UnicodeDecodeError):
            dtstart = locale['default_timezone'].localize(dtstart)
            dtend = locale['default_timezone'].localize(dtend)

    event = icalendar.Event()
    text = ' '.join(dtime_list)
    if not description or not location:
        summary = text.split(' :: ', 1)[0]
        try:
            description = text.split(' :: ', 1)[1]
        except IndexError:
            pass
    else:
        summary = text

    if description:
        event.add('description', description)
    if location:
        event.add('location', location)
    if repeat and repeat != "none":
        if repeat in ["daily", "weekly", "monthly", "yearly"]:
            rrule_settings = {'freq': repeat}
            if until:
                until_date = None
                for fun, dformat in [(datetimefstr, locale['datetimeformat']),
                                     (datetimefstr, locale['longdatetimeformat']),
                                     (timefstr, locale['timeformat']),
                                     (datetimefstr, locale['dateformat']),
                                     (datetimefstr, locale['longdateformat'])]:
                    try:
                        until_date = fun(until, dformat)
                        break
                    except ValueError:
                        pass
                if until_date is None:
                    logger.fatal("Cannot parse until date: '{}'\nPlease have a look "
                                 "at the documentation.".format(until))
                    raise FatalError()
                rrule_settings['until'] = until_date

            event.add('rrule', rrule_settings)
        else:
            logger.fatal("Invalid value for the repeat option. \
                    Possible values are: daily, weekly, monthly or yearly")
            raise FatalError()

    event.add('dtstart', dtstart)
    event.add('dtend', dtend)
    event.add('dtstamp', _now())
    event.add('summary', summary)
    event.add('uid', generate_random_uid())  # TODO add proper UID
    return event
Beispiel #26
0
def construct_event(date_list,
                    timeformat,
                    dateformat,
                    longdateformat,
                    datetimeformat,
                    longdatetimeformat,
                    default_timezone,
                    defaulttimelen=60,
                    defaultdatelen=1,
                    encoding='utf-8',
                    description=None,
                    location=None,
                    repeat=None,
                    _now=datetime.now,
                    **kwargs):
    """takes a list of strings and constructs a vevent from it

    :param encoding: the encoding of your terminal, should be a valid encoding
    :type encoding: str
    :param _now: function that returns now, used for testing

    the parts of the list can be either of these:
        * datetime datetime description
            start and end datetime specified, if no year is given, this year
            is used, if the second datetime has no year, the same year as for
            the first datetime object will be used, unless that would make
            the event end before it begins, in which case the next year is
            used
        * datetime time description
            end date will be same as start date, unless that would make the
            event end before it has started, then the next day is used as
            end date
        * datetime description
            event will last for defaulttime
        * time time description
            event starting today at the first time and ending today at the
            second time, unless that would make the event end before it has
            started, then the next day is used as end date
        * time description
            event starting today at time, lasting for the default length
        * date date description
            all day event starting on the first and ending on the last event
        * date description
            all day event starting at given date and lasting for default length

    datetime should match datetimeformat or longdatetimeformat
    time should match timeformat

    where description is the unused part of the list
    see tests for examples

    """
    today = datetime.today()

    all_day = False

    # looking for start datetime
    try:
        # first two elements are a date and a time
        dtstart = datetimefstr(date_list, datetimeformat, longdatetimeformat)
    except ValueError:
        try:
            # first element is a time
            dtstart = timefstr(date_list, timeformat)
        except ValueError:
            try:
                # first element is a date (and since second isn't a time this
                # is an all-day-event
                dtstart = datetimefstr(date_list, dateformat, longdateformat)
                all_day = True
            except ValueError:
                logger.fatal("Cannot parse: '{}'\nPlease have a look at "
                             "the documentation.".format(' '.join(date_list)))
                raise FatalError()

    # now looking for the end
    if all_day:
        try:
            # second element must be a date, too
            dtend = datetimefstr(date_list, dateformat, longdateformat)
            dtend = dtend + timedelta(days=1)
        except ValueError:
            # if it isn't we expect it to be the summary and use defaultdatelen
            # as event length
            dtend = dtstart + timedelta(days=defaultdatelen)
        # test if dtend's year is this year, but dtstart's year is not
        if dtend.year == today.year and dtstart.year != today.year:
            dtend = datetime(dtstart.year, *dtend.timetuple()[1:6])

        if dtend < dtstart:
            dtend = datetime(dtend.year + 1, *dtend.timetuple()[1:6])

    else:
        try:
            # next element datetime
            dtend = datetimefstr(date_list, datetimeformat, longdatetimeformat,
                                 dtstart.year)
        except ValueError:
            try:
                # next element time only
                dtend = timefstr(date_list, timeformat)
                dtend = datetime(*(dtstart.timetuple()[:3] +
                                   dtend.timetuple()[3:5]))
            except ValueError:
                dtend = dtstart + timedelta(minutes=defaulttimelen)

    if dtend < dtstart:
        dtend = datetime(*dtstart.timetuple()[0:3] + dtend.timetuple()[3:5])
    if dtend < dtstart:
        dtend = dtend + timedelta(days=1)
    if all_day:
        dtstart = dtstart.date()
        dtend = dtend.date()

    else:
        try:
            # next element is a valid Olson db timezone string
            dtstart = pytz.timezone(date_list[0]).localize(dtstart)
            dtend = pytz.timezone(date_list[0]).localize(dtend)
            date_list.pop(0)
        except (pytz.UnknownTimeZoneError, UnicodeDecodeError):
            dtstart = default_timezone.localize(dtstart)
            dtend = default_timezone.localize(dtend)

    event = icalendar.Event()
    text = to_unicode(' '.join(date_list), encoding)
    if not description or not location:
        summary = text.split(' :: ', 1)[0]
        try:
            description = text.split(' :: ', 1)[1]
        except IndexError:
            pass
    else:
        summary = text

    if description:
        event.add('description', description)
    if location:
        event.add('location', location)
    if repeat:
        if repeat in ["daily", "weekly", "monthly", "yearly"]:
            event.add('rrule', {'freq': repeat})
        else:
            logger.fatal("Invalid value for the repeat option. \
                    Possible values are: daily, weekly, monthly or yearly")
            raise FatalError()

    event.add('dtstart', dtstart)
    event.add('dtend', dtend)
    event.add('dtstamp', _now())
    event.add('summary', summary)
    event.add('uid', generate_random_uid())  # TODO add proper UID
    return event