Exemple #1
0
 def test_save_and_load(self):
     # previously, when reloading matches, they wouldn't be reloaded as namedtuples
     f = io.BytesIO()
     self.results.save_to_xml(f)
     f.seek(0)
     self.results.load_from_xml(f, self.get_file)
     first(self.results.groups[0].matches).percentage
Exemple #2
0
 def test_save_and_load(self):
     # previously, when reloading matches, they wouldn't be reloaded as namedtuples
     f = io.BytesIO()
     self.results.save_to_xml(f)
     f.seek(0)
     self.results.load_from_xml(f, self.get_file)
     first(self.results.groups[0].matches).percentage
Exemple #3
0
 def test_simple(self):
     l = [NamedObject("foo bar"), NamedObject("bar bleh"), NamedObject("a b c foo")]
     r = getmatches(l)
     eq_(2, len(r))
     m = first(m for m in r if m.percentage == 50) #"foo bar" and "bar bleh"
     assert_match(m, 'foo bar', 'bar bleh')
     m = first(m for m in r if m.percentage == 33) #"foo bar" and "a b c foo"
     assert_match(m, 'foo bar', 'a b c foo')
Exemple #4
0
 def test_simple(self):
     l = [NamedObject("foo bar"),NamedObject("bar bleh"),NamedObject("a b c foo")]
     r = getmatches(l)
     eq_(2,len(r))
     m = first(m for m in r if m.percentage == 50) #"foo bar" and "bar bleh"
     assert_match(m, 'foo bar', 'bar bleh')
     m = first(m for m in r if m.percentage == 33) #"foo bar" and "a b c foo"
     assert_match(m, 'foo bar', 'a b c foo')
Exemple #5
0
 def amount(self, value):
     assert self.can_set_amount
     if value == self.amount:
         return
     debit = first(s for s in self.splits if s.debit)
     credit = first(s for s in self.splits if s.credit)
     debit_account = debit.account if debit is not None else None
     credit_account = credit.account if credit is not None else None
     self.splits = [Split(self, debit_account, value), Split(self, credit_account, -value)]
Exemple #6
0
 def amount(self, value):
     assert self.can_set_amount
     if value == self.amount:
         return
     debit = first(s for s in self.splits if s.debit)
     credit = first(s for s in self.splits if s.credit)
     debit_account = debit.account if debit is not None else None
     credit_account = credit.account if credit is not None else None
     self.splits = [
         Split(self, debit_account, value),
         Split(self, credit_account, -value)
     ]
Exemple #7
0
 def test_simple(self):
     itemList = [
         NamedObject("foo bar"),
         NamedObject("bar bleh"),
         NamedObject("a b c foo"),
     ]
     r = getmatches(itemList)
     eq_(2, len(r))
     m = first(m for m in r
               if m.percentage == 50)  # "foo bar" and "bar bleh"
     assert_match(m, "foo bar", "bar bleh")
     m = first(m for m in r
               if m.percentage == 33)  # "foo bar" and "a b c foo"
     assert_match(m, "foo bar", "a b c foo")
Exemple #8
0
 def balance(self, strong_split=None, keep_two_splits=False):
     # strong_split is the split that was last edited.
     # keep_two_splits is a flag that, if enabled and that a strong_split is defined, causes the
     # balance process to set the weak split to the invert value of the strong one so that we
     # don't end up with a third unassigned split.
     
     # When the flag is false and for the special case where there is 2 splits on the same
     # "side" and a strong split, we reverse the weak split.
     if len(self.splits) == 2 and strong_split is not None:
         weak_split = self.splits[0] if self.splits[0] is not strong_split else self.splits[1]
         if keep_two_splits:
             weak_split.amount = -strong_split.amount
         elif (weak_split.amount > 0) == (strong_split.amount > 0): # on the same side
             weak_split.amount *= -1
     splits_with_amount = [s for s in self.splits if s.amount]
     if splits_with_amount and not allsame(s.amount.currency for s in splits_with_amount):
         self.balance_currencies(strong_split)
         return
     imbalance = sum(s.amount for s in self.splits)
     if not imbalance:
         return
     is_unassigned = lambda s: s.account is None and s is not strong_split
     imbalance = sum(s.amount for s in self.splits)
     if imbalance:
         unassigned = first(s for s in self.splits if is_unassigned(s))
         if unassigned is not None:
             unassigned.amount -= imbalance
         else:
             self.splits.append(Split(self, None, -imbalance))
     for split in self.splits[:]:
         if is_unassigned(split) and split.amount == 0:
             self.splits.remove(split)
Exemple #9
0
 def selected_target_index(self):
     target_name = self.layout.target_account_name
     if not target_name:
         return 0
     index = first(i for i, t in enumerate(self._target_accounts)
                   if t.name == target_name)
     return index + 1 if index is not None else 0
Exemple #10
0
 def save_edits(self):
     elements = self.app.selected_elements
     len(elements) == 1
     first(elements).text = self.edit_text
     # XXX Ok, this is a horrible hack, but I'll straighten this out later
     # we need to refresh the elements table
     self.app.notify('elements_changed')
Exemple #11
0
 def select_pane_of_type(self, pane_type, clear_filter=True):
     if clear_filter:
         self.document.filter_string = ''
     index = first(i for i, p in enumerate(self.panes) if p.view.VIEW_TYPE == pane_type)
     if index is None:
         self._add_pane(self._create_pane(pane_type))
     else:
         self.current_pane_index = index
Exemple #12
0
 def open_selected_plugin(self):
     index = self.plugin_list.selected_index
     if index is None:
         return
     plugin_name = self.plugin_list[index]
     plugin = first(p for p in self.mainwindow.app.plugins if p.NAME == plugin_name)
     if plugin is not None:
         self.mainwindow.set_current_pane_with_plugin(plugin)
Exemple #13
0
 def account_changed(self):
     self._undo_stack_changed()
     tochange = first(
         p for p in self.panes
         if p.account is not None and p.account.name != p.label)
     if tochange is not None:
         tochange.label = tochange.account.name
         self.view.refresh_panes()
    def assign_imbalance(self):
        """Assigns remaining imbalance to the selected split.

        If the selected split is not an assigned split, does nothing.
        """
        split = first(self._selected_splits)
        if split is not None:
            self.transaction.assign_imbalance(split)
            self.split_table.refresh_splits()
    def assign_imbalance(self):
        """Assigns remaining imbalance to the selected split.

        If the selected split is not an assigned split, does nothing.
        """
        split = first(self._selected_splits)
        if split is not None:
            self.transaction.assign_imbalance(split)
            self.split_table.refresh_splits()
Exemple #16
0
 def open_selected_plugin(self):
     index = self.plugin_list.selected_index
     if index is None:
         return
     plugin_name = self.plugin_list[index]
     plugin = first(p for p in self.mainwindow.app.get_enabled_plugins()
                    if p.NAME == plugin_name)
     if plugin is not None:
         self.mainwindow.set_current_pane_with_plugin(plugin)
Exemple #17
0
 def select_pane_of_type(self, pane_type, clear_filter=True):
     if clear_filter:
         self.document.filter_string = ''
     index = first(i for i, p in enumerate(self.panes)
                   if p.view.VIEW_TYPE == pane_type)
     if index is None:
         self._add_pane(self._create_pane(pane_type))
     else:
         self.current_pane_index = index
Exemple #18
0
    def balance(self, strong_split=None, keep_two_splits=False):
        """Balance out :attr:`splits` if needed.

        A balanced transaction has all its splits making a zero sum. Balancing a transaction is
        rather easy: We sum all our splits and create an unassigned split of the opposite of that
        amount. To avoid polluting our splits, we look if we already have an unassigned split and,
        if we do, we adjust its amount instead of creating a new split.

        There's a special case to that rule, and that is when we have two splits. When those two
        splits are on the same "side" (both positive or both negative), we assume that the user has
        just reversed ``strong_split``'s side and that the logical next step is to also reverse the
        other split (the "weak" split), which we'll do.

        If ``keep_two_splits`` is true, we'll go one step further and adjust the weak split's amount
        to fit what was just entered in the strong split. If it's false, we'll create an unassigned
        split if needed.

        Easy, right? Things get a bit more complicated when a have a
        :ref:`multi-currency transaction <multi-currency-txn>`. When that happens, we do a more
        complicated balancing, which happens in :meth:`balance_currencies`.

        :param strong_split: The split that was last edited. The reason why we're balancing the
                             transaction now. If set, it will not be adjusted by the balancing
                             because we don't want to pull the rug from under our user's feet and
                             undo an edit he's just made.
        :type strong_split: :class:`Split`
        :param bool keep_two_splits: If set and if we have a two-split transaction, we'll keep it
                                     that way, adjusting the "weak" split amount as needed.
        """
        if len(self.splits) == 2 and strong_split is not None:
            weak_split = self.splits[0] if self.splits[
                0] is not strong_split else self.splits[1]
            if keep_two_splits:
                weak_split.amount = -strong_split.amount
            elif (weak_split.amount > 0) == (strong_split.amount >
                                             0):  # on the same side
                weak_split.amount *= -1
        splits_with_amount = [s for s in self.splits if s.amount]
        if splits_with_amount and not allsame(s.amount.currency
                                              for s in splits_with_amount):
            self.balance_currencies(strong_split)
            return
        imbalance = sum(s.amount for s in self.splits)
        if not imbalance:
            return
        is_unassigned = lambda s: s.account is None and s is not strong_split
        imbalance = sum(s.amount for s in self.splits)
        if imbalance:
            unassigned = first(s for s in self.splits if is_unassigned(s))
            if unassigned is not None:
                unassigned.amount -= imbalance
            else:
                self.splits.append(Split(self, None, -imbalance))
        for split in self.splits[:]:
            if is_unassigned(split) and split.amount == 0:
                self.splits.remove(split)
Exemple #19
0
 def mct_balance(self, new_split_currency):
     # when calling this, the transaction is supposed to be balanced with balance() already
     converted_amounts = (convert_amount(split.amount, new_split_currency, self.date) for split in self.splits)
     converted_total = sum(converted_amounts)
     if converted_total != 0:
         target = first(s for s in self.splits if (s.account is None) and of_currency(s.amount, new_split_currency))
         if target is not None:
             target.amount -= converted_total
         else:
             self.splits.append(Split(self, None, -converted_total))
Exemple #20
0
 def open_account(self, account):
     if account is not None:
         # Try to find a suitable pane, or add a new one
         index = first(i for i, p in enumerate(self.panes) if p.account is account)
         if index is None:
             self._add_pane(self._create_pane(PaneType.Account, account))
         else:
             self.current_pane_index = index
     elif self._current_pane.view.VIEW_TYPE == PaneType.Account:
         self.select_pane_of_type(PaneType.NetWorth)
Exemple #21
0
 def open_account(self, account):
     if account is not None:
         # Try to find a suitable pane, or add a new one
         index = first(i for i, p in enumerate(self.panes)
                       if p.account is account)
         if index is None:
             self._add_pane(self._create_pane(PaneType.Account, account))
         else:
             self.current_pane_index = index
     elif self._current_pane.view.VIEW_TYPE == PaneType.Account:
         self.select_pane_of_type(PaneType.NetWorth)
Exemple #22
0
 def elements_selected(self):
     elements = self.app.selected_elements
     self.edit_enabled = False
     if not elements:
         self.edit_text = ''
     elif len(elements) == 1:
         self.edit_text = first(elements).text
         self.edit_enabled = True
     else:
         self.edit_text = "(Multiple selection)"
     self.view.refresh_edit_text()
 def mct_balance(self):
     """Balances the mct by using xchange rates. The currency of the new split is the currency of
     the currently selected split.
     """
     self.split_table.edition_must_stop()
     split = first(self._selected_splits)
     new_split_currency = self.document.default_currency
     if split is not None and split.amount != 0:
         new_split_currency = split.amount.currency
     self.transaction.mct_balance(new_split_currency)
     self.split_table.refresh_splits()
    def mct_balance(self):
        """Balances the mct by using xchange rates.

        The currency of the new split is the currency of the currently selected split.
        """
        self.split_table.edition_must_stop()
        split = first(self._selected_splits)
        new_split_currency = self.document.default_currency
        if split is not None and split.amount != 0:
            new_split_currency = split.amount.currency
        self.transaction.mct_balance(new_split_currency)
        self.split_table.refresh_splits()
    def balance(self, strong_split=None, keep_two_splits=False):
        """Balance out :attr:`splits` if needed.

        A balanced transaction has all its splits making a zero sum. Balancing a transaction is
        rather easy: We sum all our splits and create an unassigned split of the opposite of that
        amount. To avoid polluting our splits, we look if we already have an unassigned split and,
        if we do, we adjust its amount instead of creating a new split.

        There's a special case to that rule, and that is when we have two splits. When those two
        splits are on the same "side" (both positive or both negative), we assume that the user has
        just reversed ``strong_split``'s side and that the logical next step is to also reverse the
        other split (the "weak" split), which we'll do.

        If ``keep_two_splits`` is true, we'll go one step further and adjust the weak split's amount
        to fit what was just entered in the strong split. If it's false, we'll create an unassigned
        split if needed.

        Easy, right? Things get a bit more complicated when a have a
        :ref:`multi-currency transaction <multi-currency-txn>`. When that happens, we do a more
        complicated balancing, which happens in :meth:`balance_currencies`.

        :param strong_split: The split that was last edited. The reason why we're balancing the
                             transaction now. If set, it will not be adjusted by the balancing
                             because we don't want to pull the rug from under our user's feet and
                             undo an edit he's just made.
        :type strong_split: :class:`Split`
        :param bool keep_two_splits: If set and if we have a two-split transaction, we'll keep it
                                     that way, adjusting the "weak" split amount as needed.
        """
        if len(self.splits) == 2 and strong_split is not None:
            weak_split = self.splits[0] if self.splits[0] is not strong_split else self.splits[1]
            if keep_two_splits:
                weak_split.amount = -strong_split.amount
            elif (weak_split.amount > 0) == (strong_split.amount > 0): # on the same side
                weak_split.amount *= -1
        splits_with_amount = [s for s in self.splits if s.amount]
        if splits_with_amount and not allsame(s.amount.currency for s in splits_with_amount):
            self.balance_currencies(strong_split)
            return
        imbalance = sum(s.amount for s in self.splits)
        if not imbalance:
            return
        is_unassigned = lambda s: s.account is None and s is not strong_split
        imbalance = sum(s.amount for s in self.splits)
        if imbalance:
            unassigned = first(s for s in self.splits if is_unassigned(s))
            if unassigned is not None:
                unassigned.amount -= imbalance
            else:
                self.splits.append(Split(self, None, -imbalance))
        for split in self.splits[:]:
            if is_unassigned(split) and split.amount == 0:
                self.splits.remove(split)
Exemple #26
0
 def select_layout(self, name):
     if not name:
         new_layout = self._default_layout
     else:
         new_layout = first(layout for layout in self._layouts if layout.name == name)
     if new_layout is self.layout:
         return
     self.layout = new_layout
     self.layout.adjust_columns(self._colcount)
     self.view.refresh_columns_name()
     self.view.refresh_lines()
     self.view.refresh_targets()
Exemple #27
0
def setAccelKeys(menu):
    actions = menu.actions()
    titles = [a.text() for a in actions]
    available_characters = {c.lower() for s in titles for c in s if c.isalpha()}
    for action in actions:
        text = action.text()
        c = first(c for c in text if c.lower() in available_characters)
        if c is None:
            continue
        i = text.index(c)
        newtext = text[:i] + '&' + text[i:]
        available_characters.remove(c.lower())
        action.setText(newtext)
Exemple #28
0
 def select_layout(self, name):
     if not name:
         new_layout = self._default_layout
     else:
         new_layout = first(layout for layout in self._layouts
                            if layout.name == name)
     if new_layout is self.layout:
         return
     self.layout = new_layout
     self.layout.adjust_columns(self._colcount)
     self.view.refresh_columns_name()
     self.view.refresh_lines()
     self.view.refresh_targets()
Exemple #29
0
 def continue_import(self):
     loader = self.document.loader
     loader.columns = self.layout.columns
     lines = [line for index, line in enumerate(self.lines) if not self.line_is_excluded(index)]
     loader.lines = lines
     target_name = self.layout.target_account_name
     loader.target_account = first(t for t in self._target_accounts if t.name == target_name)
     try:
         self.document.load_parsed_file_for_import()
     except FileLoadError as e:
         self.view.show_message(str(e))
     else:
         self.view.hide()
Exemple #30
0
 def fit(self, element, expandH=False, expandV=False):
     # Go through all available rects and take the first one that fits.
     fits = lambda rect: rect.width() >= element.rect.width() and rect.height() >= element.rect.height()
     fittingRect = first(r for r in self.availableRects if fits(r))
     if fittingRect is None:
         return False
     element.rect.moveTopLeft(fittingRect.topLeft())
     if expandH:
         element.rect.setWidth(fittingRect.width())
     if expandV:
         element.rect.setHeight(fittingRect.height())
     self.elements.append(element)
     element.placed()
     self._computeAvailableRects()
     return True
Exemple #31
0
    def _firstEditableIndex(self, originalIndex, columnIndexes=None):
        """Returns the first editable index in `originalIndex`'s row or None.

        If `columnIndexes` is not None, the scan for an editable index will be limited to these
        columns.
        """
        model = self.model()
        h = self._headerView()
        editedRow = originalIndex.row()
        if columnIndexes is None:
            columnIndexes = (h.logicalIndex(i) for i in range(h.count()))
        create = lambda col: model.createIndex(editedRow, col, originalIndex.internalPointer())
        scannedIndexes = (create(i) for i in columnIndexes if not h.isSectionHidden(i))
        editableIndex = first(index for index in scannedIndexes if model.flags(index) & Qt.ItemIsEditable)
        return editableIndex
Exemple #32
0
 def _do_delete_dupe(self, dupe, *args):
     if isinstance(dupe, IPhoto):
         try:
             a = app('iPhoto')
             album = a.photo_library_album()
             if album is None:
                 msg = "There are communication problems with iPhoto. Try opening iPhoto first, it might solve it."
                 raise EnvironmentError(msg)
             [photo] = album.photos[its.image_path == str(dupe.path)]()
             a.remove(photo, timeout=0)
         except ValueError:
             msg = "Could not find photo '{}' in iPhoto Library".format(
                 str(dupe.path))
             raise EnvironmentError(msg)
         except (CommandError, RuntimeError) as e:
             raise EnvironmentError(str(e))
     if isinstance(dupe, AperturePhoto):
         try:
             a = app('Aperture')
             # I'm flying blind here. In my own test library, all photos are in an album with the
             # id "LibraryFolder", so I'm going to guess that it's the case at least most of the
             # time. As a safeguard, if we don't find any library with that id, we'll use the
             # first album.
             # Now, about deleting: All attempts I've made at sending photos to trash failed,
             # even with normal applescript. So, what we're going to do here is to create a
             # "dupeGuru Trash" project and tell the user to manually send those photos to trash.
             libraries = a.libraries()
             library = first(l for l in libraries
                             if l.id == 'LibraryFolder')
             if library is None:
                 library = libraries[0]
             trash_project = a.projects["dupeGuru Trash"]
             if trash_project.exists():
                 trash_project = trash_project()
             else:
                 trash_project = library.make(
                     new=k.project,
                     with_properties={k.name: "dupeGuru Trash"})
             [photo] = library.image_versions[its.id == dupe.db_id]()
             photo.move(to=trash_project)
         except (IndexError, ValueError):
             msg = "Could not find photo '{}' in Aperture Library".format(
                 str(dupe.path))
             raise EnvironmentError(msg)
         except (CommandError, RuntimeError) as e:
             raise EnvironmentError(str(e))
     else:
         DupeGuruBase._do_delete_dupe(self, dupe, *args)
Exemple #33
0
 def fit(self, element, expandH=False, expandV=False):
     # Go through all available rects and take the first one that fits.
     fits = lambda rect: rect.width() >= element.rect.width(
     ) and rect.height() >= element.rect.height()
     fittingRect = first(r for r in self.availableRects if fits(r))
     if fittingRect is None:
         return False
     element.rect.moveTopLeft(fittingRect.topLeft())
     if expandH:
         element.rect.setWidth(fittingRect.width())
     if expandV:
         element.rect.setHeight(fittingRect.height())
     self.elements.append(element)
     element.placed()
     self._computeAvailableRects()
     return True
Exemple #34
0
def setAccelKeys(menu):
    actions = menu.actions()
    titles = [a.text() for a in actions]
    available_characters = {
        c.lower()
        for s in titles for c in s if c.isalpha()
    }
    for action in actions:
        text = action.text()
        c = first(c for c in text if c.lower() in available_characters)
        if c is None:
            continue
        i = text.index(c)
        newtext = text[:i] + '&' + text[i:]
        available_characters.remove(c.lower())
        action.setText(newtext)
Exemple #35
0
 def continue_import(self):
     loader = self.mainwindow.loader
     loader.columns = self.layout.columns
     lines = [
         line for index, line in enumerate(self.lines)
         if not self.line_is_excluded(index)
     ]
     loader.lines = lines
     target_name = self.layout.target_account_name
     loader.target_account = first(t for t in self._target_accounts
                                   if t.name == target_name)
     try:
         self.mainwindow.load_parsed_file_for_import()
     except FileLoadError as e:
         self.view.show_message(str(e))
     else:
         self.view.hide()
Exemple #36
0
 def refresh_panes(self):
     if not hasattr(self.document, 'loader'):
         return
     self.refresh_targets()
     accounts = [a for a in self.document.loader.accounts if a.is_balance_sheet_account() and a.entries]
     parsing_date_format = DateFormat.from_sysformat(self.document.loader.parsing_date_format)
     for account in accounts:
         target_account = None
         if self.document.loader.target_account is not None:
             target_account = self.document.loader.target_account
         elif account.reference:
             target_account = first(t for t in self.target_accounts if t.reference == account.reference)
         self.panes.append(AccountPane(account, target_account, parsing_date_format))
     # XXX Should replace by _update_selected_pane()?
     self._refresh_target_selection()
     self._refresh_swap_list_items()
     self.import_table.refresh()
Exemple #37
0
    def _firstEditableIndex(self, originalIndex, columnIndexes=None):
        """Returns the first editable index in `originalIndex`'s row or None.

        If `columnIndexes` is not None, the scan for an editable index will be limited to these
        columns.
        """
        model = self.model()
        h = self._headerView()
        editedRow = originalIndex.row()
        if columnIndexes is None:
            columnIndexes = (h.logicalIndex(i) for i in range(h.count()))
        create = lambda col: model.createIndex(editedRow, col,
                                               originalIndex.internalPointer())
        scannedIndexes = (create(i) for i in columnIndexes
                          if not h.isSectionHidden(i))
        editableIndex = first(index for index in scannedIndexes
                              if model.flags(index) & Qt.ItemIsEditable)
        return editableIndex
Exemple #38
0
    def balance_currencies(self, strong_split=None):
        """Balances a :ref:`multi-currency transaction <multi-currency-txn>`.

        Balancing out multi-currencies transasctions can be real easy because we consider that
        currencies can never mix (and we would never make the gross mistake of using market exchange
        rates to do our balancing), so, if we have at least one split on each side of different
        currencies, we consider ourselves balanced and do nothing.

        However, we might be in a situation of "logical imbalance", which means that the transaction
        doesn't logically makes sense. For example, if all our splits are on the same side, we can't
        possibly balance out. If we have EUR and CAD splits, that CAD splits themselves balance out
        but that EUR splits are all on the same side, we have a logical imbalance.

        This method finds those imbalance and fix them by creating unsassigned splits balancing out
        every currency being in that situation.

        :param strong_split: The split that was last edited. See :meth:`balance`.
        :type strong_split: :class:`Split`
        """
        splits_with_amount = [s for s in self.splits if s.amount != 0]
        if not splits_with_amount:
            return
        currency2balance = defaultdict(int)
        for split in splits_with_amount:
            currency2balance[split.amount.currency] += split.amount
        imbalanced = stripfalse(currency2balance.values()
                                )  # filters out zeros (balances currencies)
        # For a logical imbalance to be possible, all imbalanced amounts must be on the same side
        if imbalanced and allsame(amount > 0 for amount in imbalanced):
            unassigned = [
                s for s in self.splits
                if s.account is None and s is not strong_split
            ]
            for amount in imbalanced:
                split = first(
                    s for s in unassigned
                    if s.amount == 0 or s.amount.currency == amount.currency)
                if split is not None:
                    if split.amount == amount:  # we end up with a null split, remove it
                        self.splits.remove(split)
                    else:
                        split.amount -= amount  # adjust
                else:
                    self.splits.append(Split(self, None, -amount))
Exemple #39
0
 def _do_delete_dupe(self, dupe, *args):
     if isinstance(dupe, IPhoto):
         try:
             a = app("iPhoto")
             album = a.photo_library_album()
             if album is None:
                 msg = "There are communication problems with iPhoto. Try opening iPhoto first, it might solve it."
                 raise EnvironmentError(msg)
             [photo] = album.photos[its.image_path == str(dupe.path)]()
             a.remove(photo, timeout=0)
         except ValueError:
             msg = "Could not find photo '{}' in iPhoto Library".format(str(dupe.path))
             raise EnvironmentError(msg)
         except (CommandError, RuntimeError) as e:
             raise EnvironmentError(str(e))
     if isinstance(dupe, AperturePhoto):
         try:
             a = app("Aperture")
             # I'm flying blind here. In my own test library, all photos are in an album with the
             # id "LibraryFolder", so I'm going to guess that it's the case at least most of the
             # time. As a safeguard, if we don't find any library with that id, we'll use the
             # first album.
             # Now, about deleting: All attempts I've made at sending photos to trash failed,
             # even with normal applescript. So, what we're going to do here is to create a
             # "dupeGuru Trash" project and tell the user to manually send those photos to trash.
             libraries = a.libraries()
             library = first(l for l in libraries if l.id == "LibraryFolder")
             if library is None:
                 library = libraries[0]
             trash_project = a.projects["dupeGuru Trash"]
             if trash_project.exists():
                 trash_project = trash_project()
             else:
                 trash_project = library.make(new=k.project, with_properties={k.name: "dupeGuru Trash"})
             [photo] = library.image_versions[its.id == dupe.db_id]()
             photo.move(to=trash_project)
         except (IndexError, ValueError):
             msg = "Could not find photo '{}' in Aperture Library".format(str(dupe.path))
             raise EnvironmentError(msg)
         except (CommandError, RuntimeError) as e:
             raise EnvironmentError(str(e))
     else:
         DupeGuruBase._do_delete_dupe(self, dupe, *args)
Exemple #40
0
 def balance_currencies(self, strong_split=None):
     splits_with_amount = [s for s in self.splits if s.amount != 0]
     if not splits_with_amount:
         return
     currency2balance = defaultdict(int)
     for split in splits_with_amount:
         currency2balance[split.amount.currency] += split.amount
     imbalanced = stripfalse(currency2balance.values()) # filters out zeros (balances currencies)
     # For a logical imbalance to be possible, all imbalanced amounts must be on the same side
     if imbalanced and allsame(amount > 0 for amount in imbalanced):
         unassigned = [s for s in self.splits if s.account is None and s is not strong_split]
         for amount in imbalanced:
             split = first(s for s in unassigned if s.amount == 0 or s.amount.currency == amount.currency)
             if split is not None:
                 if split.amount == amount: # we end up with a null split, remove it
                     self.splits.remove(split)
                 else:
                     split.amount -= amount # adjust
             else:
                 self.splits.append(Split(self, None, -amount))
    def mct_balance(self, new_split_currency):
        """Balances a :ref:`multi-currency transaction <multi-currency-txn>` using exchange rates.

        *This balancing doesn't occur automatically, it is a user-initiated action.*

        Sums up the value of all splits in ``new_split_currency``, using exchange rates for
        :attr:`date`. If not zero, create a new unassigned split with the opposite of that amount.

        Of course, we need to have called :meth:`balance` before we can call this.

        :param new_split_currency: :class:`.Currency`
        """
        converted_amounts = (convert_amount(split.amount, new_split_currency, self.date) for split in self.splits)
        converted_total = sum(converted_amounts)
        if converted_total != 0:
            target = first(s for s in self.splits if (s.account is None) and of_currency(s.amount, new_split_currency))
            if target is not None:
                target.amount -= converted_total
            else:
                self.splits.append(Split(self, None, -converted_total))
Exemple #42
0
 def _set_panes(self, pane_data):
     # Replace opened panes with new panes from `pane_data`, which is a [(pane_type, arg)]
     self._current_pane = None
     self._current_pane_index = -1
     for pane in self.panes:
         pane.view.disconnect()
     self.panes = []
     for pane_type, arg in pane_data:
         if pane_type >= PaneType.Plugin:
             plugin = first(p for p in self.app.plugins if p.NAME == arg)
             if plugin is not None:
                 self.panes.append(self._create_pane_from_plugin(plugin))
             else:
                 self.panes.append(self._create_pane(PaneType.NetWorth))
         else:
             try:
                 self.panes.append(self._create_pane(pane_type, account=arg))
             except ValueError:
                 self.panes.append(self._create_pane(PaneType.NetWorth))
     self.view.refresh_panes()
     self.current_pane_index = 0
Exemple #43
0
 def copy_or_move(self, dupe, copy: bool, destination: str, dest_type: DestType):
     source_path = dupe.path
     location_path = first(p for p in self.directories if dupe.path in p)
     dest_path = Path(destination)
     if dest_type in {DestType.Relative, DestType.Absolute}:
         # no filename, no windows drive letter
         source_base = source_path.remove_drive_letter().parent()
         if dest_type == DestType.Relative:
             source_base = source_base[location_path:]
         dest_path = dest_path[source_base]
     if not dest_path.exists():
         dest_path.makedirs()
     # Add filename to dest_path. For file move/copy, it's not required, but for folders, yes.
     dest_path = dest_path[source_path.name]
     logging.debug("Copy/Move operation from '%s' to '%s'", source_path, dest_path)
     # Raises an EnvironmentError if there's a problem
     if copy:
         smart_copy(source_path, dest_path)
     else:
         smart_move(source_path, dest_path)
         self.clean_empty_dirs(source_path.parent())
Exemple #44
0
 def copy_or_move(self, dupe, copy: bool, destination: str, dest_type: DestType):
     source_path = dupe.path
     location_path = first(p for p in self.directories if dupe.path in p)
     dest_path = Path(destination)
     if dest_type in {DestType.Relative, DestType.Absolute}:
         # no filename, no windows drive letter
         source_base = source_path.remove_drive_letter()[:-1]
         if dest_type == DestType.Relative:
             source_base = source_base[location_path:]
         dest_path = dest_path + source_base
     if not dest_path.exists():
         dest_path.makedirs()
     # Add filename to dest_path. For file move/copy, it's not required, but for folders, yes.
     dest_path = dest_path + source_path[-1]
     logging.debug("Copy/Move operation from '%s' to '%s'", source_path, dest_path)
     # Raises an EnvironmentError if there's a problem
     if copy:
         smart_copy(source_path, dest_path)
     else:
         smart_move(source_path, dest_path)
         self.clean_empty_dirs(source_path[:-1])
    def balance_currencies(self, strong_split=None):
        """Balances a :ref:`multi-currency transaction <multi-currency-txn>`.

        Balancing out multi-currencies transasctions can be real easy because we consider that
        currencies can never mix (and we would never make the gross mistake of using market exchange
        rates to do our balancing), so, if we have at least one split on each side of different
        currencies, we consider ourselves balanced and do nothing.

        However, we might be in a situation of "logical imbalance", which means that the transaction
        doesn't logically makes sense. For example, if all our splits are on the same side, we can't
        possibly balance out. If we have EUR and CAD splits, that CAD splits themselves balance out
        but that EUR splits are all on the same side, we have a logical imbalance.

        This method finds those imbalance and fix them by creating unsassigned splits balancing out
        every currency being in that situation.

        :param strong_split: The split that was last edited. See :meth:`balance`.
        :type strong_split: :class:`Split`
        """
        splits_with_amount = [s for s in self.splits if s.amount != 0]
        if not splits_with_amount:
            return
        currency2balance = defaultdict(int)
        for split in splits_with_amount:
            currency2balance[split.amount.currency] += split.amount
        imbalanced = stripfalse(currency2balance.values()) # filters out zeros (balances currencies)
        # For a logical imbalance to be possible, all imbalanced amounts must be on the same side
        if imbalanced and allsame(amount > 0 for amount in imbalanced):
            unassigned = [s for s in self.splits if s.account is None and s is not strong_split]
            for amount in imbalanced:
                split = first(s for s in unassigned if s.amount == 0 or s.amount.currency == amount.currency)
                if split is not None:
                    if split.amount == amount: # we end up with a null split, remove it
                        self.splits.remove(split)
                    else:
                        split.amount -= amount # adjust
                else:
                    self.splits.append(Split(self, None, -amount))
Exemple #46
0
 def _set_panes(self, pane_data):
     # Replace opened panes with new panes from `pane_data`, which is a [(pane_type, arg)]
     self._current_pane = None
     self._current_pane_index = -1
     for pane in self.panes:
         pane.view.disconnect()
     self.panes = []
     for pane_type, arg in pane_data:
         if pane_type >= PaneType.Plugin:
             plugin = first(p for p in self.app.get_enabled_plugins()
                            if p.plugin_id() == arg)
             if plugin is not None:
                 self.panes.append(self._create_pane_from_plugin(plugin))
             else:
                 self.panes.append(self._create_pane(PaneType.NetWorth))
         else:
             try:
                 self.panes.append(self._create_pane(pane_type,
                                                     account=arg))
             except ValueError:
                 self.panes.append(self._create_pane(PaneType.NetWorth))
     self.view.refresh_panes()
     self.current_pane_index = 0
Exemple #47
0
    def mct_balance(self, new_split_currency):
        """Balances a :ref:`multi-currency transaction <multi-currency-txn>` using exchange rates.

        *This balancing doesn't occur automatically, it is a user-initiated action.*

        Sums up the value of all splits in ``new_split_currency``, using exchange rates for
        :attr:`date`. If not zero, create a new unassigned split with the opposite of that amount.

        Of course, we need to have called :meth:`balance` before we can call this.

        :param new_split_currency: :class:`.Currency`
        """
        converted_amounts = (convert_amount(split.amount, new_split_currency,
                                            self.date)
                             for split in self.splits)
        converted_total = sum(converted_amounts)
        if converted_total != 0:
            target = first(s for s in self.splits if (s.account is None)
                           and of_currency(s.amount, new_split_currency))
            if target is not None:
                target.amount -= converted_total
            else:
                self.splits.append(Split(self, None, -converted_total))
Exemple #48
0
 def _load(self):
     budget = first(self.mainwindow.selected_budgets)
     self._load_budget(budget)
Exemple #49
0
 def _load(self):
     schedule = first(self.mainwindow.selected_schedules)
     self._load_schedule(schedule)
Exemple #50
0
 def account_changed(self):
     self._undo_stack_changed()
     tochange = first(p for p in self.panes if p.account is not None and p.account.name != p.label)
     if tochange is not None:
         tochange.label = tochange.account.name
         self.view.refresh_panes()
Exemple #51
0
 def get_line(self, line_header):
     return first(line for line in self.lines if line.header == line_header)
Exemple #52
0
 def _load(self):
     schedule = first(self.mainwindow.selected_schedules)
     self._load_schedule(schedule)
Exemple #53
0
 def delete_entries(self, entries):
     from_account = first(entries).account
     transactions = dedupe(e.transaction for e in entries)
     self.delete_transactions(transactions, from_account=from_account)
Exemple #54
0
 def selected_target_index(self):
     target_name = self.layout.target_account_name
     if not target_name:
         return 0
     index = first(i for i, t in enumerate(self._target_accounts) if t.name == target_name)
     return index + 1 if index is not None else 0
Exemple #55
0
 def get_line(self, line_header):
     return first(line for line in self.lines if line.header == line_header)