ebk 0.1.0__py3-none-any.whl → 0.3.2__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.

Potentially problematic release.


This version of ebk might be problematic. Click here for more details.

Files changed (84) hide show
  1. ebk/__init__.py +35 -0
  2. ebk/ai/__init__.py +23 -0
  3. ebk/ai/knowledge_graph.py +443 -0
  4. ebk/ai/llm_providers/__init__.py +21 -0
  5. ebk/ai/llm_providers/base.py +230 -0
  6. ebk/ai/llm_providers/ollama.py +362 -0
  7. ebk/ai/metadata_enrichment.py +396 -0
  8. ebk/ai/question_generator.py +328 -0
  9. ebk/ai/reading_companion.py +224 -0
  10. ebk/ai/semantic_search.py +434 -0
  11. ebk/ai/text_extractor.py +394 -0
  12. ebk/cli.py +2828 -680
  13. ebk/config.py +260 -22
  14. ebk/db/__init__.py +37 -0
  15. ebk/db/migrations.py +180 -0
  16. ebk/db/models.py +526 -0
  17. ebk/db/session.py +144 -0
  18. ebk/decorators.py +132 -0
  19. ebk/exports/base_exporter.py +218 -0
  20. ebk/exports/html_library.py +1390 -0
  21. ebk/exports/html_utils.py +117 -0
  22. ebk/exports/hugo.py +7 -3
  23. ebk/exports/jinja_export.py +287 -0
  24. ebk/exports/multi_facet_export.py +164 -0
  25. ebk/exports/symlink_dag.py +479 -0
  26. ebk/extract_metadata.py +76 -7
  27. ebk/library_db.py +899 -0
  28. ebk/plugins/__init__.py +42 -0
  29. ebk/plugins/base.py +502 -0
  30. ebk/plugins/hooks.py +444 -0
  31. ebk/plugins/registry.py +500 -0
  32. ebk/repl/__init__.py +9 -0
  33. ebk/repl/find.py +126 -0
  34. ebk/repl/grep.py +174 -0
  35. ebk/repl/shell.py +1677 -0
  36. ebk/repl/text_utils.py +320 -0
  37. ebk/search_parser.py +413 -0
  38. ebk/server.py +1633 -0
  39. ebk/services/__init__.py +11 -0
  40. ebk/services/import_service.py +442 -0
  41. ebk/services/tag_service.py +282 -0
  42. ebk/services/text_extraction.py +317 -0
  43. ebk/similarity/__init__.py +77 -0
  44. ebk/similarity/base.py +154 -0
  45. ebk/similarity/core.py +445 -0
  46. ebk/similarity/extractors.py +168 -0
  47. ebk/similarity/metrics.py +376 -0
  48. ebk/vfs/__init__.py +101 -0
  49. ebk/vfs/base.py +301 -0
  50. ebk/vfs/library_vfs.py +124 -0
  51. ebk/vfs/nodes/__init__.py +54 -0
  52. ebk/vfs/nodes/authors.py +196 -0
  53. ebk/vfs/nodes/books.py +480 -0
  54. ebk/vfs/nodes/files.py +155 -0
  55. ebk/vfs/nodes/metadata.py +385 -0
  56. ebk/vfs/nodes/root.py +100 -0
  57. ebk/vfs/nodes/similar.py +165 -0
  58. ebk/vfs/nodes/subjects.py +184 -0
  59. ebk/vfs/nodes/tags.py +371 -0
  60. ebk/vfs/resolver.py +228 -0
  61. ebk-0.3.2.dist-info/METADATA +755 -0
  62. ebk-0.3.2.dist-info/RECORD +69 -0
  63. {ebk-0.1.0.dist-info → ebk-0.3.2.dist-info}/WHEEL +1 -1
  64. ebk-0.3.2.dist-info/licenses/LICENSE +21 -0
  65. ebk/imports/__init__.py +0 -0
  66. ebk/imports/calibre.py +0 -144
  67. ebk/imports/ebooks.py +0 -116
  68. ebk/llm.py +0 -58
  69. ebk/manager.py +0 -44
  70. ebk/merge.py +0 -308
  71. ebk/streamlit/__init__.py +0 -0
  72. ebk/streamlit/__pycache__/__init__.cpython-310.pyc +0 -0
  73. ebk/streamlit/__pycache__/display.cpython-310.pyc +0 -0
  74. ebk/streamlit/__pycache__/filters.cpython-310.pyc +0 -0
  75. ebk/streamlit/__pycache__/utils.cpython-310.pyc +0 -0
  76. ebk/streamlit/app.py +0 -185
  77. ebk/streamlit/display.py +0 -168
  78. ebk/streamlit/filters.py +0 -151
  79. ebk/streamlit/utils.py +0 -58
  80. ebk/utils.py +0 -311
  81. ebk-0.1.0.dist-info/METADATA +0 -457
  82. ebk-0.1.0.dist-info/RECORD +0 -29
  83. {ebk-0.1.0.dist-info → ebk-0.3.2.dist-info}/entry_points.txt +0 -0
  84. {ebk-0.1.0.dist-info → ebk-0.3.2.dist-info}/top_level.txt +0 -0
ebk/vfs/resolver.py ADDED
@@ -0,0 +1,228 @@
1
+ """Path resolution for the Virtual File System.
2
+
3
+ Handles path parsing and navigation (cd, ls semantics).
4
+ """
5
+
6
+ from pathlib import PurePosixPath
7
+ from typing import Optional, List, Tuple
8
+
9
+ from ebk.vfs.base import Node, DirectoryNode, SymlinkNode
10
+
11
+
12
+ class PathResolver:
13
+ """Resolves paths in the VFS and handles navigation.
14
+
15
+ This class provides the core navigation logic for cd, ls, etc.
16
+ It handles:
17
+ - Absolute paths: /books/42/title
18
+ - Relative paths: ../other, ./files
19
+ - Special paths: ., .., ~
20
+ - Symlink resolution
21
+ """
22
+
23
+ def __init__(self, root: DirectoryNode):
24
+ """Initialize path resolver.
25
+
26
+ Args:
27
+ root: Root node of the VFS
28
+ """
29
+ self.root = root
30
+
31
+ def resolve(
32
+ self,
33
+ path: str,
34
+ current: DirectoryNode,
35
+ follow_symlinks: bool = True,
36
+ ) -> Optional[Node]:
37
+ """Resolve a path to a node.
38
+
39
+ Args:
40
+ path: Path to resolve (absolute or relative)
41
+ current: Current working directory
42
+ follow_symlinks: Whether to follow symlinks
43
+
44
+ Returns:
45
+ Resolved node or None if path doesn't exist
46
+ """
47
+ # Handle empty path
48
+ if not path or path == ".":
49
+ return current
50
+
51
+ # Parse path
52
+ parts = self._parse_path(path)
53
+
54
+ # Start from root or current directory
55
+ if path.startswith("/"):
56
+ node = self.root
57
+ else:
58
+ node = current
59
+
60
+ # Navigate through path parts
61
+ for part in parts:
62
+ if part == "." or part == "":
63
+ continue
64
+ elif part == "..":
65
+ # Go to parent
66
+ if node.parent is not None:
67
+ node = node.parent
68
+ # Stay at root if already at root
69
+ else:
70
+ # Navigate to child
71
+ if not isinstance(node, DirectoryNode):
72
+ # Can't cd into a file
73
+ return None
74
+
75
+ child = node.get_child(part)
76
+ if child is None:
77
+ return None
78
+
79
+ # Follow symlinks if requested
80
+ if follow_symlinks and isinstance(child, SymlinkNode):
81
+ child = self.resolve(child.target_path, current, follow_symlinks=True)
82
+ if child is None:
83
+ return None
84
+
85
+ node = child
86
+
87
+ return node
88
+
89
+ def resolve_directory(
90
+ self,
91
+ path: str,
92
+ current: DirectoryNode,
93
+ ) -> Optional[DirectoryNode]:
94
+ """Resolve a path to a directory node.
95
+
96
+ Args:
97
+ path: Path to resolve
98
+ current: Current working directory
99
+
100
+ Returns:
101
+ Directory node or None if path doesn't exist or isn't a directory
102
+ """
103
+ node = self.resolve(path, current)
104
+ if node is None or not isinstance(node, DirectoryNode):
105
+ return None
106
+ return node
107
+
108
+ def normalize_path(self, path: str, current: DirectoryNode) -> str:
109
+ """Normalize a path to absolute form.
110
+
111
+ Args:
112
+ path: Path to normalize
113
+ current: Current working directory
114
+
115
+ Returns:
116
+ Normalized absolute path
117
+ """
118
+ # Resolve to node first
119
+ node = self.resolve(path, current)
120
+ if node is None:
121
+ # Path doesn't exist, do best effort normalization
122
+ return self._normalize_nonexistent(path, current)
123
+
124
+ return node.get_path()
125
+
126
+ def complete_path(
127
+ self,
128
+ partial: str,
129
+ current: DirectoryNode,
130
+ ) -> List[str]:
131
+ """Get completion candidates for a partial path.
132
+
133
+ Used for tab completion.
134
+
135
+ Args:
136
+ partial: Partial path to complete
137
+ current: Current working directory
138
+
139
+ Returns:
140
+ List of completion candidates
141
+ """
142
+ # Split into directory part and filename part
143
+ if "/" in partial:
144
+ dir_part, file_part = partial.rsplit("/", 1)
145
+ if partial.startswith("/"):
146
+ dir_part = "/" + dir_part if dir_part else "/"
147
+ else:
148
+ dir_part = ""
149
+ file_part = partial
150
+
151
+ # Resolve directory
152
+ if dir_part:
153
+ dir_node = self.resolve_directory(dir_part, current)
154
+ else:
155
+ dir_node = current
156
+
157
+ if dir_node is None:
158
+ return []
159
+
160
+ # Get children and filter by prefix
161
+ children = dir_node.list_children()
162
+ candidates = []
163
+
164
+ for child in children:
165
+ if child.name.startswith(file_part):
166
+ if dir_part:
167
+ candidates.append(f"{dir_part}/{child.name}")
168
+ else:
169
+ candidates.append(child.name)
170
+
171
+ # Add trailing slash for directories
172
+ if isinstance(child, DirectoryNode):
173
+ candidates[-1] += "/"
174
+
175
+ return candidates
176
+
177
+ def _parse_path(self, path: str) -> List[str]:
178
+ """Parse a path into parts.
179
+
180
+ Args:
181
+ path: Path to parse
182
+
183
+ Returns:
184
+ List of path components
185
+ """
186
+ # Use PurePosixPath for Unix-style path handling
187
+ posix_path = PurePosixPath(path)
188
+
189
+ # Get parts (excluding the root /)
190
+ parts = posix_path.parts
191
+ if parts and parts[0] == "/":
192
+ parts = parts[1:]
193
+
194
+ return list(parts)
195
+
196
+ def _normalize_nonexistent(self, path: str, current: DirectoryNode) -> str:
197
+ """Normalize a path that doesn't exist.
198
+
199
+ Args:
200
+ path: Path to normalize
201
+ current: Current working directory
202
+
203
+ Returns:
204
+ Best-effort normalized path
205
+ """
206
+ if path.startswith("/"):
207
+ # Already absolute
208
+ return str(PurePosixPath(path))
209
+
210
+ # Make relative path absolute
211
+ current_path = current.get_path()
212
+ combined = PurePosixPath(current_path) / path
213
+ return str(combined)
214
+
215
+
216
+ class PathError(Exception):
217
+ """Error resolving a path."""
218
+ pass
219
+
220
+
221
+ class NotADirectoryError(PathError):
222
+ """Attempted to cd into a non-directory."""
223
+ pass
224
+
225
+
226
+ class NotFoundError(PathError):
227
+ """Path does not exist."""
228
+ pass