1#!/usr/bin/env python3
2"""
3
4"""
5# ruff: noqa: N805
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 pathlib as pl
16import re
17import time
18import types
19import urllib
20import weakref
21from uuid import UUID, uuid1
22
23# ##-- end stdlib imports
24
25# ##-- types
26# isort: off
27import abc
28import collections.abc
29from typing import TYPE_CHECKING, cast, assert_type, assert_never
30from typing import Generic, NewType
31# Protocols:
32from typing import Protocol, runtime_checkable
33# Typing Decorators:
34from typing import no_type_check, final, override, overload
35from pydantic import BaseModel, Field, model_validator, field_validator, ValidationError
36import urllib.parse
37
38if TYPE_CHECKING:
39 from jgdv import Maybe, Rx
40 from typing import Final
41 from typing import ClassVar, Any, LiteralString
42 from typing import Never, Self, Literal
43 from typing import TypeGuard
44 from collections.abc import Iterable, Iterator, Callable, Generator
45 from collections.abc import Sequence, Mapping, MutableMapping, Hashable
46
47 from jgdv.files.tags import SubstitutionFile
48
49 type UrlParseResult = urllib.parse.ParseResult
50
51# isort: on
52# ##-- end types
53
54##-- logging
55logging = logmod.getLogger(__name__)
56##-- end logging
57
[docs]
58class Bookmark(BaseModel):
59 """A Single Bookmark in a collection."""
60 url : str
61 tags : set[str] = set()
62 name : str = "No Name"
63 _tag_sep : ClassVar[str] = " : "
64 _tag_norm_re : ClassVar[Rx] = re.compile(" +")
65
[docs]
66 @classmethod
67 def build[T:Bookmark](cls:type[T], line:str, sep:Maybe[str]=None) -> T:
68 """
69 Build a bookmark from a line of a bookmark file
70 """
71 url : str
72 tags : list
73 sep = sep or Bookmark._tag_sep
74 tags = []
75 match [x.strip() for x in line.split(sep)]:
76 case []:
77 msg = "Bad line passed to Bookmark"
78 raise TypeError(msg)
79 case [url]:
80 logging.warning("No Tags for: %s", url)
81 case [url, *tags]:
82 pass
83
84 return cls(url=url,
85 tags=set(tags))
86
87
98
99 @override
100 def __hash__(self) -> int:
101 return hash(self.url)
102
103 @override
104 def __eq__(self, other:object) -> bool:
105 match other:
106 case Bookmark() as o:
107 return self.url == o.url
108 case _:
109 return False
110
111 def __lt__(self, other:object) -> bool:
112 match other:
113 case Bookmark() as o:
114 return self.url < o.url
115 case _:
116 return False
117
118 @override
119 def __str__(self) -> str:
120 sep = Bookmark._tag_sep
121 tags = sep.join(sorted(self.tags))
122 return f"{self.url}{sep}{tags}"
123
[docs]
124 @property
125 def url_comps(self) -> UrlParseResult:
126 return urllib.parse.urlparse(self.url)
127
[docs]
128 def merge(self, other:Bookmark) -> Bookmark:
129 """ Merge two bookmarks' tags together,
130 creating a new bookmark
131 """
132 assert(self == other)
133 merged = Bookmark(url=self.url,
134 tags=self.tags.union(other.tags),
135 name=self.name)
136 return merged
137
[docs]
138 def clean(self, subs:SubstitutionFile) -> None:
139 """
140 run tag substitutions on all tags in the bookmark
141 """
142 cleaned_tags = set()
143 for tag in self.tags:
144 cleaned_tags.update(subs.sub(tag))
145
146 self.tags = cleaned_tags