<commit_before>import json
from datetime import datetime, timedelta
from time import mktime

import click
import parsedatetime
import urwid
from dateutil.tz import tzlocal

from . import widgets

_palette = [
    ('error', 'light red', '')
]


class EditState:
    none = object()
    saved = object()


class TodoEditor:
    """
    The UI for a single todo entry.
    """

    def __init__(self, todo, lists, formatter):
        """
        :param model.Todo todo: The todo object which will be edited.
        """

        self.todo = todo
        self.lists = lists
        self.formatter = formatter
        self.saved = EditState.none
        self._loop = None

        self._msg_text = urwid.Text('')

        if todo.due:
            # TODO: use proper date_format
            due = formatter.format_date(todo.due)
        else:
            due = ""

        if todo.start:
            # TODO: use proper date_format
            dtstart = formatter.format_date(todo.start)
        else:
            dtstart = ''

        self._summary = widgets.ExtendedEdit(parent=self,
                                             edit_text=todo.summary)
        self._description = widgets.ExtendedEdit(
            parent=self,
            edit_text=todo.description,
            multiline=True,
        )
        self._location = widgets.ExtendedEdit(
            parent=self,
            edit_text=todo.location
        )
        self._due = widgets.ExtendedEdit(parent=self, edit_text=due)
        self._dtstart = widgets.ExtendedEdit(parent=self, edit_text=dtstart)
        self._completed = urwid.CheckBox("", state=todo.is_completed)
        self._urgent = urwid.CheckBox("", state=todo.priority != 0)

        save_btn = urwid.Button('Save', on_press=self._save)
        cancel_text = urwid.Text('Hit Ctrl-C to cancel, F1 for help.')
        buttons = urwid.Columns([(8, save_btn), cancel_text], dividechars=2)

        pile_items = []
        for label, field in [("Summary", self._summary),
                             ("Description", self._description),
                             ("Location", self._location),
                             ("Due", self._due),
                             ("Start", self._dtstart),
                             ("Completed", self._completed),
                             ("Urgent", self._urgent),
                             ]:
            label = urwid.Text(label + ":", align='right')
            column = urwid.Columns([(13, label), field], dividechars=1)
            pile_items.append(('pack', column))

        grid = urwid.Pile(pile_items)
        spacer = urwid.Divider()

        self._ui_content = items = [grid, spacer, self._msg_text, buttons]
        self._ui = urwid.ListBox(items)

        self._help_text = urwid.Text(
            '\n\nGlobal:\n'
            ' F1: Toggle help\n'
            ' Ctrl-C: Cancel\n'
            ' Ctrl-S: Save (only works if not a shell shortcut already)\n\n'
            'In Textfields:\n'
            + '\n'.join(' {}: {}'.format(k, v) for k, v
                        in widgets.ExtendedEdit.HELP)
        )

    def _toggle_help(self):
        if self._ui_content[-1] is self._help_text:
            self._ui_content.pop()
        else:
            self._ui_content.append(self._help_text)
        self._loop.draw_screen()

    def message(self, text):
        self._msg_text.set_text(text)

    def edit(self):
        """
        Shows the UI for editing a given todo. Returns True if modifications
        were saved.
        """
        self._loop = urwid.MainLoop(
            self._ui,
            palette=_palette,
            unhandled_input=self._keypress,
            handle_mouse=False,
        )
        try:
            self._loop.run()
        except Exception:
            try:  # Try to leave terminal in usable state
                self._loop.stop()
            except Exception:
                pass
            raise
        self._loop = None
        return self.saved

    def _save(self, btn=None):
        try:
            self._save_inner()
        except Exception as e:
            self.message(('error', str(e)))
        else:
            self.saved = EditState.saved
            raise urwid.ExitMainLoop()

    def _save_inner(self):
        self.todo.summary = self.summary
        self.todo.description = self.description
        self.todo.location = self.location
        self.todo.due = self.formatter.parse_date(self.due)
        self.todo.start = self.formatter.parse_date(self.dtstart)

        self.todo.is_completed = self._completed.get_state()

        # If it was already non-zero, keep it that way. Let's not overwrite
        # values 1 thru 8.
        if self._urgent.get_state() and not self.todo.priority:
            self.todo.priority = 9
        elif not self._urgent.get_state():
            self.todo.priority = 0

        # TODO: categories
        # TODO: comment
        # TODO: priority (0: undef. 1: max, 9: min)

        # https://tools.ietf.org/html/rfc5545#section-3.8
        # geo (lat, lon)
        # RESOURCE: the main room

    def _cancel(self, btn):
        raise urwid.ExitMainLoop()

    def _keypress(self, key):
        if key.lower() == 'f1':
            self._toggle_help()
        elif key == 'ctrl s':
            self._save()

    @property
    def summary(self):
        return self._summary.edit_text

    @property
    def description(self):
        return self._description.edit_text

    @property
    def location(self):
        return self._location.edit_text

    @property
    def due(self):
        return self._due.edit_text

    @property
    def dtstart(self):
        return self._dtstart.edit_text


class TodoFormatter:

    # This one looks good with [X]
    compact_format = \
        "{id:3d} [{completed}] {urgent} {due} {summary} {list}{percent}"
    # compact_format = "{completed} {urgent}  {due}  {summary}"

    def __init__(self, date_format):
        self.date_format = date_format
        self._localtimezone = tzlocal()
        self.now = datetime.now().replace(tzinfo=self._localtimezone)
        self.tomorrow = self.now.date() + timedelta(days=1)

        # An empty date which should be used in case no date is present
        self.date_width = len(self.now.strftime(date_format))
        self.empty_date = " " * self.date_width
        # Map special dates to the special string we need to return
        self.special_dates = {
            self.now.date(): "Today".rjust(self.date_width, " "),
            self.tomorrow: "Tomorrow".rjust(self.date_width, " "),
        }
        self._parsedatetime_calendar = parsedatetime.Calendar()

    def compact(self, todo):
        """
        Returns a brief representation of a task, suitable for displaying
        on-per-line.

        :param Todo todo: The todo component.
        """
        # completed = "✓" if todo.percent_complete == 100 else " "
        completed = "X" if todo.is_completed else " "
        percent = todo.percent_complete or ''
        if percent:
            percent = " ({}%)".format(percent)
        urgent = " " if todo.priority in [None, 0] else "!"

        due = self.format_date(todo.due)
        if todo.due and todo.due <= self.now and not todo.is_completed:
            due = click.style(due, fg='red')

        summary = todo.summary
        list = self.format_database(todo.list)

        return self.compact_format.format(
            completed=completed,
            due=due,
            id=todo.id,
            list=list,
            percent=percent,
            summary=summary,
            urgent=urgent,
        )

    def detailed(self, todo):
        """
        Returns a detailed representation of a task.

        :param Todo todo: The todo component.
        """
        rv = self.compact(todo)
        if todo.description:
            rv = "{}\n\n{}".format(rv, todo.description)
        return rv

    def format_date(self, date):
        """
        :param datetime.date date: a datetime object
        Returns date in the following format:
        * if date == today or tomorrow: "Today" or "Tomorrow"
        * else: return a string representing that date
        * if no date is supplied, it returns empty_date
        """
        if date:
            if date in self.special_dates:
                rv = self.special_dates[date]
            else:
                rv = date.strftime(self.date_format)
            return rv
        else:
            return self.empty_date<commit_msg>Realize date to a datetime object<commit_after>import json
from datetime import datetime, timedelta
from time import mktime

import click
import parsedatetime
import urwid
from dateutil.tz import tzlocal

from . import widgets

_palette = [
    ('error', 'light red', '')
]


class EditState:
    none = object()
    saved = object()


class TodoEditor:
    """
    The UI for a single todo entry.
    """

    def __init__(self, todo, lists, formatter):
        """
        :param model.Todo todo: The todo object which will be edited.
        """

        self.todo = todo
        self.lists = lists
        self.formatter = formatter
        self.saved = EditState.none
        self._loop = None

        self._msg_text = urwid.Text('')

        if todo.due:
            # TODO: use proper date_format
            due = formatter.format_date(todo.due)
        else:
            due = ""

        if todo.start:
            # TODO: use proper date_format
            dtstart = formatter.format_date(todo.start)
        else:
            dtstart = ''

        self._summary = widgets.ExtendedEdit(parent=self,
                                             edit_text=todo.summary)
        self._description = widgets.ExtendedEdit(
            parent=self,
            edit_text=todo.description,
            multiline=True,
        )
        self._location = widgets.ExtendedEdit(
            parent=self,
            edit_text=todo.location
        )
        self._due = widgets.ExtendedEdit(parent=self, edit_text=due)
        self._dtstart = widgets.ExtendedEdit(parent=self, edit_text=dtstart)
        self._completed = urwid.CheckBox("", state=todo.is_completed)
        self._urgent = urwid.CheckBox("", state=todo.priority != 0)

        save_btn = urwid.Button('Save', on_press=self._save)
        cancel_text = urwid.Text('Hit Ctrl-C to cancel, F1 for help.')
        buttons = urwid.Columns([(8, save_btn), cancel_text], dividechars=2)

        pile_items = []
        for label, field in [("Summary", self._summary),
                             ("Description", self._description),
                             ("Location", self._location),
                             ("Due", self._due),
                             ("Start", self._dtstart),
                             ("Completed", self._completed),
                             ("Urgent", self._urgent),
                             ]:
            label = urwid.Text(label + ":", align='right')
            column = urwid.Columns([(13, label), field], dividechars=1)
            pile_items.append(('pack', column))

        grid = urwid.Pile(pile_items)
        spacer = urwid.Divider()

        self._ui_content = items = [grid, spacer, self._msg_text, buttons]
        self._ui = urwid.ListBox(items)

        self._help_text = urwid.Text(
            '\n\nGlobal:\n'
            ' F1: Toggle help\n'
            ' Ctrl-C: Cancel\n'
            ' Ctrl-S: Save (only works if not a shell shortcut already)\n\n'
            'In Textfields:\n'
            + '\n'.join(' {}: {}'.format(k, v) for k, v
                        in widgets.ExtendedEdit.HELP)
        )

    def _toggle_help(self):
        if self._ui_content[-1] is self._help_text:
            self._ui_content.pop()
        else:
            self._ui_content.append(self._help_text)
        self._loop.draw_screen()

    def message(self, text):
        self._msg_text.set_text(text)

    def edit(self):
        """
        Shows the UI for editing a given todo. Returns True if modifications
        were saved.
        """
        self._loop = urwid.MainLoop(
            self._ui,
            palette=_palette,
            unhandled_input=self._keypress,
            handle_mouse=False,
        )
        try:
            self._loop.run()
        except Exception:
            try:  # Try to leave terminal in usable state
                self._loop.stop()
            except Exception:
                pass
            raise
        self._loop = None
        return self.saved

    def _save(self, btn=None):
        try:
            self._save_inner()
        except Exception as e:
            self.message(('error', str(e)))
        else:
            self.saved = EditState.saved
            raise urwid.ExitMainLoop()

    def _save_inner(self):
        self.todo.summary = self.summary
        self.todo.description = self.description
        self.todo.location = self.location
        self.todo.due = self.formatter.parse_date(self.due)
        self.todo.start = self.formatter.parse_date(self.dtstart)

        self.todo.is_completed = self._completed.get_state()

        # If it was already non-zero, keep it that way. Let's not overwrite
        # values 1 thru 8.
        if self._urgent.get_state() and not self.todo.priority:
            self.todo.priority = 9
        elif not self._urgent.get_state():
            self.todo.priority = 0

        # TODO: categories
        # TODO: comment
        # TODO: priority (0: undef. 1: max, 9: min)

        # https://tools.ietf.org/html/rfc5545#section-3.8
        # geo (lat, lon)
        # RESOURCE: the main room

    def _cancel(self, btn):
        raise urwid.ExitMainLoop()

    def _keypress(self, key):
        if key.lower() == 'f1':
            self._toggle_help()
        elif key == 'ctrl s':
            self._save()

    @property
    def summary(self):
        return self._summary.edit_text

    @property
    def description(self):
        return self._description.edit_text

    @property
    def location(self):
        return self._location.edit_text

    @property
    def due(self):
        return self._due.edit_text

    @property
    def dtstart(self):
        return self._dtstart.edit_text


class TodoFormatter:

    # This one looks good with [X]
    compact_format = \
        "{id:3d} [{completed}] {urgent} {due} {summary} {list}{percent}"
    # compact_format = "{completed} {urgent}  {due}  {summary}"

    def __init__(self, date_format):
        self.date_format = date_format
        self._localtimezone = tzlocal()
        self.now = datetime.now().replace(tzinfo=self._localtimezone)
        self.tomorrow = self.now.date() + timedelta(days=1)

        # An empty date which should be used in case no date is present
        self.date_width = len(self.now.strftime(date_format))
        self.empty_date = " " * self.date_width
        # Map special dates to the special string we need to return
        self.special_dates = {
            self.now.date(): "Today".rjust(self.date_width, " "),
            self.tomorrow: "Tomorrow".rjust(self.date_width, " "),
        }
        self._parsedatetime_calendar = parsedatetime.Calendar()

    def compact(self, todo):
        """
        Returns a brief representation of a task, suitable for displaying
        on-per-line.

        :param Todo todo: The todo component.
        """
        # completed = "✓" if todo.percent_complete == 100 else " "
        completed = "X" if todo.is_completed else " "
        percent = todo.percent_complete or ''
        if percent:
            percent = " ({}%)".format(percent)
        urgent = " " if todo.priority in [None, 0] else "!"

        due = self.format_date(todo.due)
        if todo.due and todo.due <= self.now and not todo.is_completed:
            due = click.style(due, fg='red')

        summary = todo.summary
        list = self.format_database(todo.list)

        return self.compact_format.format(
            completed=completed,
            due=due,
            id=todo.id,
            list=list,
            percent=percent,
            summary=summary,
            urgent=urgent,
        )

    def detailed(self, todo):
        """
        Returns a detailed representation of a task.

        :param Todo todo: The todo component.
        """
        rv = self.compact(todo)
        if todo.description:
            rv = "{}\n\n{}".format(rv, todo.description)
        return rv

    def format_date(self, date):
        """
        :param datetime.datetime date: a datetime object
        Returns date in the following format:
        * if date == today or tomorrow: "Today" or "Tomorrow"
        * else: return a string representing that date
        * if no date is supplied, it returns empty_date
        """
        assert isinstance(date, datetime)
        if date:
            if date.date() in self.special_dates:
                rv = self.special_dates[date.date()]
            else:
                rv = date.strftime(self.date_format)
            return rv
        else:
            return self.empty_date
{'fraction_non_alphanumeric': 0.10685365567421665, 'fraction_numerical': 0.003403356413566483, 'mean_word_length': 1.7725719863347975, 'pattern_counts': {'":': 2, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1299919', 'n_tokens_mistral': 5003, 'n_tokens_neox': 4799, 'n_words': 1195}