<commit_before>"""Line-oriented UI."""

# https://docs.python.org/3/library/cmd.html

import cmd
import logging
import shlex

import common
from common import View

import pyutils.misc

import synd
from synd import Flag

import util

log = logging.getLogger(__name__)
messager = util.Messager(__name__)
# message = common.message
# feedback = common.feedback


class UI(cmd.Cmd):
    """Show entries interactively."""
    intro = 'Welcome'
    separator = ';'  # Separator for multiple commands on one line.

    def __init__(self, proc, view=None):
        super().__init__()
        self.proc = proc
        self.entries = None
        self._i = 0
        self.lastcmd = 'n'
        self.lastline = self.lastcmd
        self.n_cmds = 0  # Number of commands given, if several.
        self.view = View(**proc.view)
        if view is not None:
            self.view.update(view)
        self.proc.cache_feeds = True
        self.read_data()

    def __del__(self):
        # super().__del__()  # Doesn't have one.
        # d = dict(directory=self.view.directory, entry=None, feed=None)
        d = dict(entry=None, feed=None)
        d['directory'] = [str(x) for x in self.view.directory]
        if self.entries:
            d.update(entry=self.entry.guid, feed=self.feed.url)
        self.proc.session.update(d)
        self.proc.session.write()
        self.write_data()

    def read_data(self, force=False):
        """Read data."""
        # s = 'Reading with f={flags}, s={sortkey}, n={number}, S={sortkey2}'
        # message(s.format(**self.view))
        messager.msg('Reading with {}'.format(self.view))
        if force:
            self.proc.clear_cache()
        entries = list(self.proc.generate_entries(view=self.view))
        synd.sort_entries(entries, self.view.sortkey2)
        self.entries = entries

    def write_data(self, force=False):
        """Write data."""
        feeds = self.proc.open_feeds.values()
        messager.msg('Writing {} feeds'.format(len(feeds)))
        for feed in feeds:
            feed.write(force=force)

    def run(self, jump=False, guid=None, url=None):
        """Run user interaction loop."""
        if jump:
            guid = guid or self.proc.session.get('entry')
            url = url or self.proc.session.get('feed')
            if guid is not None or url is not None:
                self.jump(guid, url)
        if self.entries:
            self.cmdloop()
        else:
            messager.msg('There are no entries. Bye!')

    def jump(self, guid=None, url=None):
        """Jump to entry or feed."""
        index = None
        for i, e in enumerate(self.entries):
            if guid is not None and e.guid == guid:
                index = i
                msg = 'Jumping to entry...'
                break
            if index is None and url is not None and e.feed.url == url:
                index = i
                msg = 'Jumping to feed...'
        if index is None:
            msg = 'Cannot find jump destination.'
        else:
            self.i = index
        messager.msg(msg)

    @property
    def i(self):
        return pyutils.misc.clamp(self._i, 0, len(self.entries) - 1)

    @i.setter
    def i(self, value):
        self._i = pyutils.misc.clamp(value, 0, len(self.entries) - 1)

    @property
    def entry(self):
        """Current entry."""
        return self.entries[self.i]

    @property
    def feed(self):
        """Feed of current entry."""
        return self.entry.feed

    def get_row(self, i=None):
        """Get a line of info for the prompt or line selector."""
        if i is None:
            i = self.i
        entry = self.entries[i]
        d = dict(
            i=i,
            n=len(self.entries),
            nfe=entry.feed._nentries or -1,
            flag=entry.flag.value,
            dir=entry.feed.directory,
            d=util.time_fmt(entry.date, fmt='compactdate'),
            dl=sum(x.path.exists() for x in entry.encs()),
            nl=sum(x.is_normalized() for x in entry.encs()),
            t=entry.abbreviated_title,
            )
        s = '{flag}{dl}{nl} {i:2d}/{n:2d} {nfe:2d} {d} {dir}●{t}'
        return s.format(**d)

    def get_prompt(self):
        """Get command prompt string. Long entry titles are shortened."""
        s = '{{x}} [{default}] '.format(default=self.lastline)
        x = pyutils.misc.truncate(self.get_row(), reserved=len(s))
        return s.format(x=x)
<commit_msg>added get_row_with_duplicate_entries()<commit_after>"""Line-oriented UI."""

# https://docs.python.org/3/library/cmd.html

import cmd
import logging
import shlex

import common
from common import View

import pyutils.misc

import synd
from synd import Flag

import util

log = logging.getLogger(__name__)
messager = util.Messager(__name__)
# message = common.message
# feedback = common.feedback


class UI(cmd.Cmd):
    """Show entries interactively."""
    intro = 'Welcome'
    separator = ';'  # Separator for multiple commands on one line.

    def __init__(self, proc, view=None):
        super().__init__()
        self.proc = proc
        self.entries = None
        self._i = 0
        self.lastcmd = 'n'
        self.lastline = self.lastcmd
        self.n_cmds = 0  # Number of commands given, if several.
        self.view = View(**proc.view)
        if view is not None:
            self.view.update(view)
        self.proc.cache_feeds = True
        self.read_data()

    def __del__(self):
        # super().__del__()  # Doesn't have one.
        # d = dict(directory=self.view.directory, entry=None, feed=None)
        d = dict(entry=None, feed=None)
        d['directory'] = [str(x) for x in self.view.directory]
        if self.entries:
            d.update(entry=self.entry.guid, feed=self.feed.url)
        self.proc.session.update(d)
        self.proc.session.write()
        self.write_data()

    def read_data(self, force=False):
        """Read data."""
        # s = 'Reading with f={flags}, s={sortkey}, n={number}, S={sortkey2}'
        # message(s.format(**self.view))
        messager.msg('Reading with {}'.format(self.view))
        if force:
            self.proc.clear_cache()
        entries = list(self.proc.generate_entries(view=self.view))
        synd.sort_entries(entries, self.view.sortkey2)
        self.entries = entries

    def write_data(self, force=False):
        """Write data."""
        feeds = self.proc.open_feeds.values()
        messager.msg('Writing {} feeds'.format(len(feeds)))
        for feed in feeds:
            feed.write(force=force)

    def run(self, jump=False, guid=None, url=None):
        """Run user interaction loop."""
        if jump:
            guid = guid or self.proc.session.get('entry')
            url = url or self.proc.session.get('feed')
            if guid is not None or url is not None:
                self.jump(guid, url)
        if self.entries:
            self.cmdloop()
        else:
            messager.msg('There are no entries. Bye!')

    def jump(self, guid=None, url=None):
        """Jump to entry or feed."""
        index = None
        for i, e in enumerate(self.entries):
            if guid is not None and e.guid == guid:
                index = i
                msg = 'Jumping to entry...'
                break
            if index is None and url is not None and e.feed.url == url:
                index = i
                msg = 'Jumping to feed...'
        if index is None:
            msg = 'Cannot find jump destination.'
        else:
            self.i = index
        messager.msg(msg)

    @property
    def i(self):
        return pyutils.misc.clamp(self._i, 0, len(self.entries) - 1)

    @i.setter
    def i(self, value):
        self._i = pyutils.misc.clamp(value, 0, len(self.entries) - 1)

    @property
    def entry(self):
        """Current entry."""
        return self.entries[self.i]

    @property
    def feed(self):
        """Feed of current entry."""
        return self.entry.feed

    def get_row(self, i=None):
        """Get a line of info for the prompt or line selector."""
        if i is None:
            i = self.i
        entry = self.entries[i]
        d = dict(
            i=i,
            n=len(self.entries),
            nfe=entry.feed._nentries or -1,
            flag=entry.flag.value,
            dir=entry.feed.directory,
            d=util.time_fmt(entry.date, fmt='compactdate'),
            dl=sum(x.path.exists() for x in entry.encs()),
            nl=sum(x.is_normalized() for x in entry.encs()),
            t=entry.abbreviated_title,
            )
        s = '{flag}{dl}{nl} {i:2d}/{n:2d} {nfe:2d} {d} {dir}●{t}'
        return s.format(**d)

    def get_row_with_duplicate_entries(self, i=None):
        """A (slow) version of get_row() with number of duplicate entries in
        other feeds.
        """
        # TODO: Rather implement duplicate finder on demand in common.py.
        if i is None:
            i = self.i
        entry = self.entries[i]
        # nhits = sum(x.guid == entry.guid for x in self.entries)
        # nhits = sum(x.link == entry.link for x in self.entries)
        # nhits = sum(x.subtitle == entry.subtitle for x in self.entries)

        def _urls(entry):
            return {x.href for x in entry.encs()}
        ext_entries = (x for x in self.entries if x.feed.directory !=
                       entry.feed.directory)
        nhits = sum(_urls(x) == _urls(entry) for x in ext_entries)
        return '{} {}'.format(nhits, self.get_row(i=i))

    def get_prompt(self):
        """Get command prompt string. Long entry titles are shortened."""
        s = '{{x}} [{default}] '.format(default=self.lastline)
        x = pyutils.misc.truncate(self.get_row(), reserved=len(s))
        return s.format(x=x)

{'fraction_non_alphanumeric': 0.13156814449917897, 'fraction_numerical': 0.0026683087027914616, 'mean_word_length': 1.9792112503821462, 'pattern_counts': {'":': 0, '<': 3, '<?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': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1391227', 'n_tokens_mistral': 3075, 'n_tokens_neox': 2966, 'n_words': 792}