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())