Source code for jgdv.files.bookmarks.collection

  1#!/usr/bin/env python3
  2"""
  3
  4"""
  5
  6# Imports:
  7from __future__ import annotations
  8
  9# ##-- stdlib imports
 10import datetime
 11import enum
 12import functools as ftz
 13import itertools as itz
 14import logging as logmod
 15import re
 16import time
 17import weakref
 18from uuid import UUID, uuid1
 19
 20# ##-- end stdlib imports
 21
 22# ##-- 1st party imports
 23from jgdv.files.bookmarks.bookmark import Bookmark
 24
 25# ##-- end 1st party imports
 26
 27# ##-- types
 28# isort: off
 29# General
 30import abc
 31import collections.abc
 32import typing
 33import types
 34from typing import cast, assert_type, assert_never
 35from typing import Generic, NewType, Never
 36from typing import no_type_check, final, override, overload
 37# Protocols and Interfaces:
 38from typing import Protocol, runtime_checkable
 39from pydantic import BaseModel, Field, model_validator, field_validator, ValidationError
 40# isort: on
 41# ##-- end types
 42
 43# ##-- type checking
 44# isort: off
 45if typing.TYPE_CHECKING:
 46    import pathlib as pl
 47    from typing import Final, ClassVar, Any, Self
 48    from typing import Literal, LiteralString
 49    from typing import TypeGuard
 50    from collections.abc import Iterable, Iterator, Callable, Generator
 51    from collections.abc import Sequence, Mapping, MutableMapping, Hashable
 52
 53    from jgdv import Maybe
 54## isort: on
 55# ##-- end type checking
 56
 57
 58##-- logging
 59logging = logmod.getLogger(__name__)
 60##-- end logging
 61
[docs] 62class BookmarkCollection(BaseModel): 63 """A container of bookmarks, 64 read from a file where each line is a bookmark url with tags. 65 """ 66 67 entries : list[Bookmark] = [] 68 ext : str = ".bookmarks" 69
[docs] 70 @staticmethod 71 def read(fpath:pl.Path) -> BookmarkCollection: 72 """ Read a file to build a bookmark collection """ 73 bookmarks = BookmarkCollection() 74 for line in (x.strip() for x in fpath.read_text().split("\n")): 75 if not bool(line): 76 continue 77 bookmarks += Bookmark.build(line) 78 79 return bookmarks
80 81 @override 82 def __str__(self) -> str: 83 return "\n".join(map(str, sorted(self.entries))) 84 85 @override 86 def __repr__(self) -> str : 87 return f"<{self.__class__.__name__}: {len(self)}>" 88 89 def __iadd__(self, value:Bookmark) -> Self: 90 return self.update(value) 91 92 @override 93 def __iter__(self) -> Iterator[Bookmark]: # type: ignore[override] 94 return iter(self.entries) 95 96 def __contains__(self, value:Bookmark) -> bool: 97 return value in self.entries 98 99 def __len__(self) -> int: 100 return len(self.entries) 101 102 @override 103 def __hash__(self) -> int: 104 return id(self) 105
[docs] 106 def update(self, *values:Bookmark|BookmarkCollection|Iterable) -> Self: 107 for val in values: 108 match val: 109 case Bookmark(): 110 self.entries.append(val) 111 case BookmarkCollection(): 112 self.entries += val.entries 113 case [*vals] | set(vals): 114 self.update(*vals) 115 case _: 116 raise TypeError(type(val)) 117 return self
118
[docs] 119 def difference(self, other:Self) -> BookmarkCollection: 120 result = BookmarkCollection() 121 for bkmk in other: 122 if bkmk not in self: 123 result += bkmk 124 125 return result
126
[docs] 127 def merge_duplicates(self) -> None: 128 deduplicated = {} 129 for x in self: 130 if x.url not in deduplicated: 131 deduplicated[x.url] = x 132 else: 133 deduplicated[x.url] = x.merge(deduplicated[x.url]) 134 135 self.entries = list(deduplicated.values())