sebsync 1.0a4__tar.gz → 1.0a5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sebsync
3
- Version: 1.0a4
3
+ Version: 1.0a5
4
4
  Summary: Synchronize Standard Ebooks catalog with local ebooks.
5
5
  Home-page: https://github.com/pbryan/sebsync/
6
6
  License: MIT
@@ -17,7 +17,6 @@ Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Topic :: Other/Nonlisted Topic
18
18
  Requires-Dist: click (>=8.1,<9.0)
19
19
  Requires-Dist: requests (>=2.32,<3.0)
20
- Project-URL: Documentation, https://github.com/pbryan/sebsync/tree/main/docs
21
20
  Description-Content-Type: text/markdown
22
21
 
23
22
  # sebsync
@@ -8,12 +8,11 @@ target-version = ['py312']
8
8
 
9
9
  [tool.poetry]
10
10
  name = "sebsync"
11
- version = "1.0a4"
11
+ version = "1.0a5"
12
12
  description = "Synchronize Standard Ebooks catalog with local ebooks."
13
13
  readme = "README.md"
14
14
  authors = ["sebsync authors"]
15
15
  homepage = "https://github.com/pbryan/sebsync/"
16
- documentation = "https://github.com/pbryan/sebsync/tree/main/docs"
17
16
  license = "MIT"
18
17
  keywords = ["ebook", "downloader", "standard", "book"]
19
18
  classifiers = [
@@ -23,12 +23,12 @@ class Status:
23
23
 
24
24
 
25
25
  _epilog = f"""
26
- The following statuses are reported for ebooks:
26
+ The following statuses are reported:
27
27
 
28
28
  \b
29
- {Status.NEW}: new (ebook found in Standard Ebooks catalog but not found locally)
30
- {Status.UPDATE}: update (new version of ebook found in Standard Ebooks catalog)
31
- {Status.EXTRA}: extraneous (local ebook was not found in Standard Ebooks catalog)
29
+ {Status.NEW}: new (downloads the new ebook to downloads directory)
30
+ {Status.UPDATE}: update (overwrites the existing local ebook with new version)
31
+ {Status.EXTRA}: extraneous (local ebook was not found in Standard Ebooks catalog)
32
32
 
33
33
  An extraneous ebook can occur when Standard Ebooks changes the identifier of a previously
34
34
  published ebook. It's a rare occurance, and it's generally safe to delete such files.
@@ -56,16 +56,24 @@ class LocalEbook:
56
56
  modified: datetime
57
57
 
58
58
 
59
- def _echo_status(path: Path, status: str) -> None:
59
+ # map type selection to link title in OPDS catalog
60
+ type_selector = {
61
+ "compatible": "Recommended compatible epub",
62
+ "kobo": "Kobo Kepub epub",
63
+ "advanced": "Advanced epub",
64
+ }
65
+
66
+
67
+ def echo_status(path: Path, status: str) -> None:
60
68
  if not _quiet:
61
69
  click.echo(f"{status} {path}")
62
70
 
63
71
 
64
- def _if_exists(path: Path) -> Path | None:
72
+ def if_exists(path: Path) -> Path | None:
65
73
  return path if path.exists() else None
66
74
 
67
75
 
68
- def _fromisoformat(text: str) -> datetime:
76
+ def fromisoformat(text: str) -> datetime:
69
77
  """Convert RFC 3339 string into datetime; compatible with Python 3.10."""
70
78
  if not text.endswith("Z"):
71
79
  raise ValueError("expecting RFC 3339 formatted string")
@@ -75,13 +83,6 @@ def _fromisoformat(text: str) -> datetime:
75
83
  )
76
84
 
77
85
 
78
- type_selector = {
79
- "compatible": "Recommended compatible epub",
80
- "kobo": "Kobo Kepub epub",
81
- "advanced": "Advanced epub",
82
- }
83
-
84
-
85
86
  def get_remote_ebooks(opds_url: str, email: str, type: str) -> dict[str, StandardEbook]:
86
87
  """Return Standard Ebooks metadata for EPUBs from the OPDS catalog."""
87
88
  ns = {"atom": "http://www.w3.org/2005/Atom", "dc": "http://purl.org/dc/terms/"}
@@ -95,7 +96,7 @@ def get_remote_ebooks(opds_url: str, email: str, type: str) -> dict[str, Standar
95
96
  title=entry.find("atom:title", ns).text,
96
97
  author=entry.find("atom:author", ns).find("atom:name", ns).text,
97
98
  href=entry.find(f".//atom:link[@title='{type_selector[type]}']", ns).attrib["href"],
98
- updated=_fromisoformat(entry.find("atom:updated", ns).text),
99
+ updated=fromisoformat(entry.find("atom:updated", ns).text),
99
100
  )
100
101
  ebooks[ebook.id] = ebook
101
102
  return ebooks
@@ -125,7 +126,7 @@ def get_local_ebooks(dir: Path) -> dict[str, LocalEbook]:
125
126
  id=id.text,
126
127
  title=metadata.find(".//dc:title", ns).text,
127
128
  path=path,
128
- modified=_fromisoformat(modified.text),
129
+ modified=fromisoformat(modified.text),
129
130
  )
130
131
  ebooks[ebook.id] = ebook
131
132
  return ebooks
@@ -157,47 +158,49 @@ def ebook_filename(ebook: StandardEbook) -> str:
157
158
  @click.command(help=__doc__, epilog=_epilog)
158
159
  @click.option(
159
160
  "--books",
160
- help="directory where local books are stored",
161
+ help="Directory where local books are stored.",
161
162
  type=click.Path(exists=True, file_okay=False, dir_okay=True, writable=True, path_type=Path),
162
- default=_if_exists(Path.home() / "Books"),
163
+ default=if_exists(Path.home() / "Books"),
163
164
  )
164
165
  @click.option(
165
166
  "--downloads",
166
- help="directory where new ebooks are downloaded",
167
+ help="Directory where new ebooks are downloaded.",
167
168
  type=click.Path(exists=True, file_okay=False, dir_okay=True, writable=True, path_type=Path),
168
- default=_if_exists(Path.home() / "Downloads"),
169
+ default=if_exists(Path.home() / "Downloads"),
169
170
  )
170
171
  @click.option(
171
172
  "--dry-run",
172
- help="perform a trial run with no changes made",
173
+ help="Perform a trial run with no changes made.",
173
174
  is_flag=True,
174
175
  )
175
176
  @click.option(
176
177
  "--email",
177
- help="email address to authenticate with Standard Ebooks",
178
+ help="Email address to authenticate with Standard Ebooks.",
178
179
  required=True,
179
180
  )
181
+ @click.help_option()
180
182
  @click.option(
181
183
  "--opds",
182
- help="URL of Standard Ebooks OPDS catalog",
184
+ help="URL of Standard Ebooks OPDS catalog.",
183
185
  default="https://standardebooks.org/feeds/opds/all",
184
186
  )
185
187
  @click.option(
186
188
  "--quiet",
187
- help="suppress non-error messages",
189
+ help="Suppress non-error messages.",
188
190
  is_flag=True,
189
191
  )
190
192
  @click.option(
191
193
  "--type",
192
- type=click.Choice(["compatible", "kobo", "advanced"]),
193
- help="EPUB type to download",
194
+ type=click.Choice(type_selector.keys()),
195
+ help="EPUB type to download.",
194
196
  default="compatible",
195
197
  )
196
198
  @click.option(
197
199
  "--verbose",
198
- help="increase verbosity",
200
+ help="Increase verbosity.",
199
201
  is_flag=True,
200
202
  )
203
+ @click.version_option(package_name="sebsync")
201
204
  def sebsync(
202
205
  books: str,
203
206
  downloads: str,
@@ -213,7 +216,7 @@ def sebsync(
213
216
  global _quiet
214
217
 
215
218
  _dry_run = dry_run
216
- _verbose = verbose
219
+ _verbose = verbose and not quiet # quiet wins
217
220
  _quiet = quiet
218
221
 
219
222
  remote_ebooks = get_remote_ebooks(opds, email, type)
@@ -229,16 +232,16 @@ def sebsync(
229
232
  for remote_ebook in remote_ebooks.values():
230
233
  if local_ebook := local_ebooks.get(remote_ebook.id):
231
234
  if remote_ebook.updated != local_ebook.modified:
232
- _echo_status(local_ebook.path, Status.UPDATE)
235
+ echo_status(local_ebook.path, Status.UPDATE)
233
236
  download_ebook(remote_ebook.href, local_ebook.path)
234
237
  else:
235
238
  path = downloads / ebook_filename(remote_ebook)
236
- _echo_status(path, Status.NEW)
239
+ echo_status(path, Status.NEW)
237
240
  download_ebook(remote_ebook.href, path)
238
241
 
239
242
  for local_ebook in local_ebooks.values():
240
243
  if local_ebook.id not in remote_ebooks:
241
- _echo_status(local_ebook.path, Status.EXTRA)
244
+ echo_status(local_ebook.path, Status.EXTRA)
242
245
 
243
246
 
244
247
  def main():
File without changes
File without changes
File without changes