oldnews 0.6.0__py3-none-any.whl → 0.7.0__py3-none-any.whl

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.
@@ -45,7 +45,7 @@ def options_after_highlight[T](
45
45
  highlight = (len(options) - highlight - 1) if direction == "previous" else highlight
46
46
  return (
47
47
  option
48
- for option in [*options[highlight:], *options[0:highlight]][1:]
48
+ for option in [*options[highlight:], *options[:highlight]][1:]
49
49
  if option_filter(option)
50
50
  )
51
51
 
@@ -7,7 +7,7 @@ from __future__ import annotations
7
7
  ##############################################################################
8
8
  # Python imports.
9
9
  from dataclasses import dataclass
10
- from typing import cast
10
+ from typing import Callable, Iterable, Iterator, cast
11
11
 
12
12
  ##############################################################################
13
13
  # OldAs imports.
@@ -157,41 +157,86 @@ class Navigation(EnhancedOptionList):
157
157
  self._expanded = get_navigation_state()
158
158
  """The IDs of the folders that are expanded."""
159
159
 
160
- def _add_subscriptions(self, parent_folder: str) -> None:
161
- """Add the subscriptions for a given parent folder.
160
+ @staticmethod
161
+ def _key(attr: str) -> Callable[[object], str]:
162
+ """Create a key to use with `sorted`.
162
163
 
163
164
  Args:
164
- parent_folder: The parent folder to add the subscriptions for.
165
+ attr: The attribute to sort on.
166
+
167
+ Returns:
168
+ A function to get a `casefold` version of the attribute.
169
+ """
170
+
171
+ def _casefold(value: object) -> str:
172
+ return cast(str, getattr(value, attr)).casefold()
173
+
174
+ return _casefold
175
+
176
+ def _viewable(
177
+ self, subscriptions: Iterable[Subscription]
178
+ ) -> Iterator[SubscriptionView]:
179
+ """Given a iterable of subscriptions, make them viewable.
180
+
181
+ Args:
182
+ subscriptions: The subscriptions to make viewable.
183
+
184
+ Yields:
185
+ Views of the subscriptions.
165
186
  """
166
- for subscription in self.subscriptions:
167
- if any(
168
- category.id == parent_folder for category in subscription.categories
169
- ):
170
- self.add_option(SubscriptionView(subscription, self.unread))
187
+ yield from (
188
+ SubscriptionView(subscription, self.unread)
189
+ for subscription in sorted(subscriptions, key=self._key("title"))
190
+ )
171
191
 
172
- def _add_folder(self, folder: Folder) -> None:
173
- """Add the given folder to the navigation.
192
+ def _gather_subscriptions_for_folder(
193
+ self, parent_folder: Folder
194
+ ) -> Iterator[SubscriptionView]:
195
+ """Gather the subscriptions for a given parent folder.
174
196
 
175
197
  Args:
176
- folder: The folder to add.
198
+ parent_folder: The parent folder to add the subscriptions for.
199
+
200
+ Yields:
201
+ The subscriptions within that folder.
202
+ """
203
+ yield from self._viewable(
204
+ subscription
205
+ for subscription in self.subscriptions
206
+ if parent_folder in subscription.categories
207
+ )
208
+
209
+ def _gather_folders(self) -> Iterator[FolderView | SubscriptionView]:
210
+ """Gather up all the folders and their subscriptions.
211
+
212
+ Yields:
213
+ Folder and subscription options.
214
+ """
215
+ for folder in sorted(self.folders, key=self._key("name")):
216
+ yield FolderView(
217
+ folder, expanded := folder.id in self._expanded, self.unread
218
+ )
219
+ if expanded:
220
+ yield from self._gather_subscriptions_for_folder(folder)
221
+
222
+ def _gather_folderless_subscrtiptions(self) -> Iterator[SubscriptionView]:
223
+ """Gather up all the subscriptions that don't live in a folder.
224
+
225
+ Yields:
226
+ Subscription options for folderless subscriptions.
177
227
  """
178
- self.add_option(
179
- FolderView(folder, expanded := folder.id in self._expanded, self.unread)
228
+ yield from self._viewable(
229
+ subscription
230
+ for subscription in self.subscriptions
231
+ if not subscription.categories
180
232
  )
181
- if expanded:
182
- self._add_subscriptions(folder.id)
183
233
 
184
234
  def _refresh_navigation(self) -> None:
185
235
  """Refresh the content of the navigation widget."""
186
236
  with self.preserved_highlight:
187
- self.clear_options()
188
- # First off, add subscriptions that lack a folder.
189
- for subscription in self.subscriptions:
190
- if not subscription.categories:
191
- self.add_option(SubscriptionView(subscription, self.unread))
192
- # Now add all the subscriptions that are within folders.
193
- for folder in self.folders:
194
- self._add_folder(folder)
237
+ self.set_options(
238
+ (*self._gather_folderless_subscrtiptions(), *self._gather_folders())
239
+ )
195
240
 
196
241
  def _watch_folders(self) -> None:
197
242
  """React to the folders being updated."""
@@ -205,6 +250,25 @@ class Navigation(EnhancedOptionList):
205
250
  """React to the unread data being updated."""
206
251
  self._refresh_navigation()
207
252
 
253
+ @work(thread=True)
254
+ def _save_state(self, state: set[str]) -> None:
255
+ """Save the folder expanded/collapsed state.
256
+
257
+ Args:
258
+ state: The state to save.
259
+ """
260
+ save_navigation_state(state)
261
+
262
+ def _set_expansion(self, new_state: set[str]) -> None:
263
+ """Set the new navigation state.
264
+
265
+ Args:
266
+ new_state: The new state to set.
267
+ """
268
+ self._expanded = new_state
269
+ self._save_state(new_state)
270
+ self._refresh_navigation()
271
+
208
272
  def _action_toggle_folder(self) -> None:
209
273
  """Action that toggles the expanded state of a folder."""
210
274
  if self.highlighted is None:
@@ -215,26 +279,15 @@ class Navigation(EnhancedOptionList):
215
279
  self.notify("Only folders can be collapsed/expanded", severity="warning")
216
280
  return
217
281
  if option.folder.id is not None:
218
- self._expanded ^= {option.folder.id}
219
- self._save_state()
220
- self._refresh_navigation()
282
+ self._set_expansion(self._expanded ^ {option.folder.id})
221
283
 
222
284
  def _action_expand_all(self) -> None:
223
285
  """Action that expands all folders."""
224
- self._expanded = {folder.id for folder in self.folders}
225
- self._save_state()
226
- self._refresh_navigation()
286
+ self._set_expansion({folder.id for folder in self.folders})
227
287
 
228
288
  def _action_collapse_all(self) -> None:
229
289
  """Action that collapses all folders."""
230
- self._expanded = set()
231
- self._save_state()
232
- self._refresh_navigation()
233
-
234
- @work(thread=True)
235
- def _save_state(self) -> None:
236
- """Save the folder expanded/collapsed state."""
237
- save_navigation_state(self._expanded)
290
+ self._set_expansion(set())
238
291
 
239
292
  @on(EnhancedOptionList.OptionSelected)
240
293
  def _handle_selection(self, message: EnhancedOptionList.OptionSelected) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oldnews
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: A terminal-based client for TheOldReader
5
5
  Keywords: atom,client,Google Reader,RSS,TheOldReader,terminal,news-reader,news
6
6
  Author: Dave Pearson
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.13
15
15
  Classifier: Programming Language :: Python :: 3.14
16
16
  Classifier: Typing :: Typed
17
17
  Requires-Dist: html-to-markdown>=2.15.0
18
- Requires-Dist: oldas>=0.6.0
18
+ Requires-Dist: oldas>=0.10.0
19
19
  Requires-Dist: textual>=6.3.0
20
20
  Requires-Dist: textual-autocomplete>=4.0.6
21
21
  Requires-Dist: textual-enhanced>=1.2.0
@@ -29,11 +29,11 @@ oldnews/screens/main.py,sha256=AuCL54UC-DHl-fyRTr1_lSS2hCBXQKizQirpWpLBazs,28618
29
29
  oldnews/screens/new_subscription.py,sha256=bk01RXWu3kvJbtm88o1mrYpll73Op3PWWl7ggIH29kk,4011
30
30
  oldnews/sync.py,sha256=OZv5JvImvzPAjV6cuCbiSoIeVy0TnfWAq-rL-Xs_ffk,10877
31
31
  oldnews/widgets/__init__.py,sha256=5VSjKswHxv2W8g0O-LWlUFS-gy7iQrIM8PGvSBhDNLU,438
32
- oldnews/widgets/_after_highlight.py,sha256=4GgMx0SYU05K3K7xFk22N6ZSn5wewnAjA062mQT6jBg,1904
32
+ oldnews/widgets/_after_highlight.py,sha256=cUJXUXElKfDrTsmy2WzKqGcxu67mlTamRBSi-VM_ddQ,1903
33
33
  oldnews/widgets/article_content.py,sha256=pv8zobd3eTsALHYmP854dd_enqy32QXGoWXEILYnWfA,3553
34
34
  oldnews/widgets/article_list.py,sha256=dSmSGlk1X94ryy0bJ6ejc2WXO0XMvTWyTxEtR1cL_5k,7428
35
- oldnews/widgets/navigation.py,sha256=QE_jyavY-A4mugEmFVOo1Ed7g9GfK4wEkrnjO5YQLtA,11019
36
- oldnews-0.6.0.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
37
- oldnews-0.6.0.dist-info/entry_points.txt,sha256=FxY6Y4IsHZubhtdd0QJG3p2kCTcXe0e-Ib_AW0qkotE,51
38
- oldnews-0.6.0.dist-info/METADATA,sha256=vVA5ZPZ3r4E1rGLVg9ibHGeZwJ62j-zAakuoVRZ6_m4,2917
39
- oldnews-0.6.0.dist-info/RECORD,,
35
+ oldnews/widgets/navigation.py,sha256=8GMjJtLFnvc9GJH7_ufChLsywPQno7HwWmBIrEnDjgo,12433
36
+ oldnews-0.7.0.dist-info/WHEEL,sha256=e_m4S054HL0hyR3CpOk-b7Q7fDX6BuFkgL5OjAExXas,80
37
+ oldnews-0.7.0.dist-info/entry_points.txt,sha256=FxY6Y4IsHZubhtdd0QJG3p2kCTcXe0e-Ib_AW0qkotE,51
38
+ oldnews-0.7.0.dist-info/METADATA,sha256=BHH_29q28IZdWENsHZ7DpnqXBxoNDznqhJK2Vg-3JQk,2918
39
+ oldnews-0.7.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.26
2
+ Generator: uv 0.9.27
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any