Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

python3 compatibility #187

Merged
merged 9 commits into from
May 2, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions khal/calendar_display.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
import calendar
import datetime

from .terminal import bstring, rstring
from click import style

from .compat import VERSION


Expand Down Expand Up @@ -63,7 +64,7 @@ def str_week(week, today):
strweek = ''
for day in week:
if day == today:
day = rstring(str(day.day).rjust(2))
day = style(str(day.day).rjust(2), reverse=True)
else:
day = str(day.day).rjust(2)
strweek = strweek + day + ' '
Expand Down Expand Up @@ -101,19 +102,23 @@ def vertical_month(month=datetime.date.today().month,
w_number = ' ' if weeknumber == 'right' else ''
calendar.setfirstweekday(firstweekday)
_calendar = calendar.Calendar(firstweekday)
khal.append(bstring(' ' + calendar.weekheader(2) + ' ' + w_number))
khal.append(
style(' ' + calendar.weekheader(2) + ' ' + w_number, bold=True)
)
for _ in range(count):
for week in _calendar.monthdatescalendar(year, month):
new_month = len([day for day in week if day.day == 1])
strweek = str_week(week, today)
if new_month:
m_name = bstring(month_abbr(week[6].month).ljust(4))
m_name = style(month_abbr(week[6].month).ljust(4), bold=True)
elif weeknumber == 'left':
m_name = bstring(' {:2} '.format(getweeknumber(week[0])))
m_name = \
style(' {:2} '.format(getweeknumber(week[0])), bold=True)
else:
m_name = ' '
if weeknumber == 'right':
w_number = bstring(' {}'.format(getweeknumber(week[0])))
w_number = \
style(' {}'.format(getweeknumber(week[0])), bold=True)
else:
w_number = ''

Expand Down
14 changes: 10 additions & 4 deletions khal/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def setproctitle(x):
from khal.log import logger
from khal.settings import get_config, InvalidSettingsError
from khal.exceptions import FatalError
from .compat import to_unicode
from .terminal import colored, get_terminal_size


Expand Down Expand Up @@ -144,7 +145,7 @@ def prepare_context(ctx, config, verbose):
sys.exit(1)

logger.debug('Using config:')
logger.debug(stringify_conf(conf).decode('utf-8'))
logger.debug(to_unicode(stringify_conf(conf), 'utf-8'))

if conf is None:
raise click.UsageError('Invalid config file, exiting.')
Expand Down Expand Up @@ -297,8 +298,10 @@ def search(ctx, search_string):
for event in events:
desc = textwrap.wrap(event.long(), term_width)
event_column.extend([colored(d, event.color) for d in desc])
click.echo('\n'.join(event_column).encode(
ctx.obj['conf']['locale']['encoding']))
click.echo(to_unicode(
'\n'.join(event_column),
ctx.obj['conf']['locale']['encoding'])
)

@cli.command()
@calendar_selector
Expand Down Expand Up @@ -340,7 +343,10 @@ def at(ctx, datetime=None):
for event in events:
desc = textwrap.wrap(event.long(), term_width)
event_column.extend([colored(d, event.color) for d in desc])
click.echo('\n'.join(event_column).encode(ctx.obj['conf']['locale']['encoding']))
click.echo(to_unicode(
'\n'.join(event_column),
ctx.obj['conf']['locale']['encoding'])
)

return cli, interactive_cli

Expand Down
27 changes: 17 additions & 10 deletions khal/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

from click import echo
from __future__ import unicode_literals

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't do this! Some Python 2 APIs exhibit weird behavior when fed unicode strings.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't aware of this. :(

Have you found any in khal's case? Some brief testing proved that everything worked, and tests pass, but I'm willing to look into this on a case-base-case basis.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably khal doesn't even use any APIs where this matters, I just wouldn't do it as a general rule.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great. So let's keep this around in khal's case. 😉

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really wouldn't do it... to argue my points, I've found this, but it doesn't go really into detail for all of the issues we could experience.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe from a readability POV (although a very weak point IMO): Because only some modules have that future-import, you can't know whether a particular string literal produces a unicode or bytestring. So we should then do this import for every module, but that means we'd have to wrap 70% of string literals with str(...) (because "native strings" is what most stdlib APIs actually expect)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The linked website only mentions a few issues in detail, and I don't see them applying to us.

Because only some modules have that future-import, you can't know whether a particular string literal produces a unicode or bytestring.

Without this import, literals are str, not bytestrings. With it, they become unicode strings (unicode). And stdlib still works with unicode strings, eg:

>>> with open(u'test', u'w') as f:
...   f.write("1")

It's not merely readability either, IMHO. When writing new code, you don't need to remember to add adding u' to new strings - and forgetting that may cause subtle unexpected error.

Personally, I don't see drawbacks with using unicode_literals on khal's codebase, nor any way this can come back and bite us in a forseeable future, so I'd much rather keep it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without this import, literals are str, not bytestrings. With it, they become unicode strings (unicode).

Somehow the rest of the sentence slipped: You first have to look at the top of the file whether unicode_literals are imported to make sense of the literals, which is IMO dangerous while reviewing PRs.

and forgetting that may cause subtle unexpected error

The opposite does too. Try the following code in Python 2, with file having nonascii characters in it:

with open('file') as f:
    print(u'output: {}'.format(f.read()))

You don't have those problems when you use native string literals (by which I mean 'asdasd') for the format string

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So basically my point is that changing literals everywhere (by importing unicode_literals) might create regressions for current Python 2 users, which are arguably the majority right now.

from click import echo, style

import datetime
import itertools
Expand All @@ -30,12 +32,13 @@
import textwrap

from khal import aux, calendar_display
from khal.compat import to_unicode
from khal.khalendar.exceptions import ReadOnlyCalendarError
from khal.exceptions import FatalError
from khal.khalendar.event import Event
from khal import __version__, __productname__
from khal.log import logger
from .terminal import bstring, colored, get_terminal_size, merge_columns
from .terminal import colored, get_terminal_size, merge_columns


def construct_daynames(daylist, longdateformat):
Expand All @@ -50,9 +53,9 @@ def construct_daynames(daylist, longdateformat):
"""
for date in daylist:
if date == datetime.date.today():
yield (date, u'Today:')
yield (date, 'Today:')
elif date == datetime.date.today() + datetime.timedelta(days=1):
yield (date, u'Tomorrow:')
yield (date, 'Tomorrow:')
else:
yield (date, date.strftime(longdateformat))

Expand Down Expand Up @@ -109,14 +112,14 @@ def get_agenda(collection, locale, dates=None,
if len(events) == 0 and len(all_day_events) == 0 and not show_all_days:
continue

event_column.append(bstring(dayname))
event_column.append(style(dayname, bold=True))
events.sort(key=lambda e: e.start)
for event in itertools.chain(all_day_events, events):
desc = textwrap.wrap(event.compact(day), width)
event_column.extend([colored(d, event.color) for d in desc])

if event_column == []:
event_column = [bstring('No events')]
event_column = [style('No events', bold=True)]
return event_column


Expand All @@ -134,6 +137,8 @@ def __init__(self, collection, date=[], firstweekday=0, encoding='utf-8',
firstweekday=firstweekday, weeknumber=weeknumber)

rows = merge_columns(calendar_column, event_column)
# XXX: Generate this as a unicode in the first place, rather than
# casting it.
echo('\n'.join(rows).encode(encoding))


Expand All @@ -144,7 +149,9 @@ def __init__(self, collection, date=None, firstweekday=0, encoding='utf-8',
term_width, _ = get_terminal_size()
event_column = get_agenda(collection, dates=date, width=term_width,
show_all_days=show_all_days, **kwargs)
echo('\n'.join(event_column).encode(encoding))
# XXX: Generate this as a unicode in the first place, rather than
# casting it.
echo(to_unicode('\n'.join(event_column), encoding))


class NewFromString(object):
Expand All @@ -170,10 +177,10 @@ def __init__(self, collection, conf, date_list, location=None, repeat=None):
'read-only'.format(collection.default_calendar_name))
sys.exit(1)
if conf['default']['print_new'] == 'event':
print(event.long())
echo(event.long())
elif conf['default']['print_new'] == 'path':
path = collection._calnames[event.calendar].path + event.href
print(path.encode(conf['locale']['encoding']))
echo(path.encode(conf['locale']['encoding']))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is always bytes.



class Interactive(object):
Expand All @@ -186,5 +193,5 @@ def __init__(self, collection, conf):
description='do something')
ui.start_pane(
pane, pane.cleanup,
program_info=u'{0} v{1}'.format(__productname__, __version__)
program_info='{0} v{1}'.format(__productname__, __version__)
)
3 changes: 2 additions & 1 deletion khal/khalendar/aux.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import pytz

from .. import log
from ..compat import to_unicode

from .exceptions import UnsupportedRecursion

Expand Down Expand Up @@ -58,7 +59,7 @@ def expand(vevent, default_tz, href=''):
vevent['DTSTART'].dt = vevent['DTSTART'].dt.replace(tzinfo=None)

if 'RRULE' in vevent:
rrulestr = vevent['RRULE'].to_ical()
rrulestr = to_unicode(vevent['RRULE'].to_ical())
rrule = dateutil.rrule.rrulestr(rrulestr, dtstart=vevent['DTSTART'].dt)

if not set(['UNTIL', 'COUNT']).intersection(vevent['RRULE'].keys()):
Expand Down
8 changes: 4 additions & 4 deletions khal/khalendar/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
respective types

"""
from __future__ import print_function
from __future__ import print_function, unicode_literals

import contextlib
import datetime
Expand Down Expand Up @@ -526,7 +526,7 @@ def check_support(vevent, href, calendar):
.format(href, calendar)
)
rdate = vevent.get('RDATE')
if rdate is not None and rdate.params.get('VALUE') == u'PERIOD':
if rdate is not None and rdate.params.get('VALUE') == 'PERIOD':
raise UpdateFailed(
'`RDATE;VALUE=PERIOD` is currently not supported by khal. '
'Therefore event {} from calendar {} will not be shown in khal.\n'
Expand All @@ -546,7 +546,7 @@ def update(self, vevent, href, etag=''):
if 'BDAY' in vcard.keys():
bday = vcard['BDAY']
try:
if bday[0:2] == u'--' and bday[3] != u'-':
if bday[0:2] == '--' and bday[3] != '-':
bday = '1900' + bday[2:]
bday = parser.parse(bday).date()
except ValueError:
Expand All @@ -557,7 +557,7 @@ def update(self, vevent, href, etag=''):
event = icalendar.Event()
event.add('dtstart', bday)
event.add('dtend', bday + datetime.timedelta(days=1))
event.add('summary', u'{}\'s birthday'.format(name))
event.add('summary', '{}\'s birthday'.format(name))
event.add('rrule', {'freq': 'YEARLY'})
event.add('uid', href)
self._update_impl(event, href, etag)
Expand Down
44 changes: 23 additions & 21 deletions khal/khalendar/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@

"""this module will the event model, hopefully soon in a cleaned up version"""

from __future__ import unicode_literals

from datetime import date, datetime, time, timedelta

import icalendar

from ..compat import unicode_type, bytes_type, iteritems
from ..compat import unicode_type, bytes_type, iteritems, to_unicode
from .aux import to_naive_utc
from ..log import logger

Expand Down Expand Up @@ -114,19 +116,19 @@ def __init__(self, ical, calendar, href=None, start=None, end=None,
def symbol_strings(self):
if self.unicode_symbols:
return dict(
recurring=u'\N{Clockwise gapped circle arrow}',
range=u'\N{Left right arrow}',
range_end=u'\N{Rightwards arrow to bar}',
range_start=u'\N{Rightwards arrow from bar}',
right_arrow=u'\N{Rightwards arrow}'
recurring='\N{Clockwise gapped circle arrow}',
range='\N{Left right arrow}',
range_end='\N{Rightwards arrow to bar}',
range_start='\N{Rightwards arrow from bar}',
right_arrow='\N{Rightwards arrow}'
)
else:
return dict(
recurring=u'R',
range=u'<->',
range_end=u'->|',
range_start=u'|->',
right_arrow=u'->'
recurring='R',
range='<->',
range_end='->|',
range_start='|->',
right_arrow='->'
)

@property
Expand Down Expand Up @@ -237,31 +239,31 @@ def long(self):
else:
startstr = self.start.strftime(self.locale['longdateformat'])
endstr = end.strftime(self.locale['longdateformat'])
rangestr = startstr + u' - ' + endstr
rangestr = startstr + ' - ' + endstr
else:
# same day
if self.start.utctimetuple()[:3] == self.end.utctimetuple()[:3]:
starttime = self.start.strftime(self.locale['timeformat'])
endtime = self.end.strftime(self.locale['timeformat'])
datestr = self.end.strftime(self.locale['longdateformat'])
rangestr = starttime + u'-' + endtime + u' ' + datestr
rangestr = starttime + '-' + endtime + ' ' + datestr
else:
startstr = self.start.strftime(self.locale['longdatetimeformat'])
endstr = self.end.strftime(self.locale['longdatetimeformat'])
rangestr = startstr + u' - ' + endstr
rangestr = startstr + ' - ' + endstr
if self.start.tzinfo.zone != self.locale['local_timezone'].zone:
# doesn't work yet
# TODO FIXME
pass

location = u'\nLocation: ' + self.location if \
self.location is not None else u''
description = u'\nDescription: ' + self.description if \
self.description is not None else u''
repitition = u'\nRepeat: ' + self.recurpattern if \
self.recurpattern is not None else u''
location = '\nLocation: ' + self.location if \
self.location is not None else ''
description = '\nDescription: ' + self.description if \
self.description is not None else ''
repitition = '\nRepeat: ' + to_unicode(self.recurpattern) if \
self.recurpattern is not None else ''

return u'{}: {}{}{}{}'.format(
return '{}: {}{}{}{}'.format(
rangestr, self.summary, location, repitition, description)

def compact(self, day):
Expand Down
23 changes: 3 additions & 20 deletions khal/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
"""all functions related to terminal display are collected here"""

try:
from itertools import izip_longest
from itertools import izip_longest as zip_longest
except ImportError:
from itertools import zip_longest as izip_longest
from itertools import zip_longest

try:
# python 3.3+
Expand Down Expand Up @@ -68,23 +68,6 @@ def get_terminal_size():
}


def rstring(string):
"""returns string as reverse color string (ANSI escape codes)

>>> rstring('test')
'\\x1b[7mtest\\x1b[0m'
"""
return RTEXT + string + NTEXT


def bstring(string):
"""returns string as bold string (ANSI escape codes)
>>> bstring('test')
'\\x1b[1mtest\\x1b[0m'
"""
return BTEXT + string + NTEXT


def colored(string, colorstring):
try:
color = COLORS[colorstring]
Expand All @@ -107,6 +90,6 @@ def merge_columns(lcolumn, rcolumn, width=25):
if missing > 0:
lcolumn = lcolumn + missing * [width * ' ']

rows = [' '.join(one) for one in izip_longest(
rows = [' '.join(one) for one in zip_longest(
lcolumn, rcolumn, fillvalue='')]
return rows
Loading