ebk 0.4.4__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.
Files changed (87) hide show
  1. ebk/__init__.py +35 -0
  2. ebk/ai/__init__.py +23 -0
  3. ebk/ai/knowledge_graph.py +450 -0
  4. ebk/ai/llm_providers/__init__.py +26 -0
  5. ebk/ai/llm_providers/anthropic.py +209 -0
  6. ebk/ai/llm_providers/base.py +295 -0
  7. ebk/ai/llm_providers/gemini.py +285 -0
  8. ebk/ai/llm_providers/ollama.py +294 -0
  9. ebk/ai/metadata_enrichment.py +394 -0
  10. ebk/ai/question_generator.py +328 -0
  11. ebk/ai/reading_companion.py +224 -0
  12. ebk/ai/semantic_search.py +433 -0
  13. ebk/ai/text_extractor.py +393 -0
  14. ebk/calibre_import.py +66 -0
  15. ebk/cli.py +6433 -0
  16. ebk/config.py +230 -0
  17. ebk/db/__init__.py +37 -0
  18. ebk/db/migrations.py +507 -0
  19. ebk/db/models.py +725 -0
  20. ebk/db/session.py +144 -0
  21. ebk/decorators.py +1 -0
  22. ebk/exports/__init__.py +0 -0
  23. ebk/exports/base_exporter.py +218 -0
  24. ebk/exports/echo_export.py +279 -0
  25. ebk/exports/html_library.py +1743 -0
  26. ebk/exports/html_utils.py +87 -0
  27. ebk/exports/hugo.py +59 -0
  28. ebk/exports/jinja_export.py +286 -0
  29. ebk/exports/multi_facet_export.py +159 -0
  30. ebk/exports/opds_export.py +232 -0
  31. ebk/exports/symlink_dag.py +479 -0
  32. ebk/exports/zip.py +25 -0
  33. ebk/extract_metadata.py +341 -0
  34. ebk/ident.py +89 -0
  35. ebk/library_db.py +1440 -0
  36. ebk/opds.py +748 -0
  37. ebk/plugins/__init__.py +42 -0
  38. ebk/plugins/base.py +502 -0
  39. ebk/plugins/hooks.py +442 -0
  40. ebk/plugins/registry.py +499 -0
  41. ebk/repl/__init__.py +9 -0
  42. ebk/repl/find.py +126 -0
  43. ebk/repl/grep.py +173 -0
  44. ebk/repl/shell.py +1677 -0
  45. ebk/repl/text_utils.py +320 -0
  46. ebk/search_parser.py +413 -0
  47. ebk/server.py +3608 -0
  48. ebk/services/__init__.py +28 -0
  49. ebk/services/annotation_extraction.py +351 -0
  50. ebk/services/annotation_service.py +380 -0
  51. ebk/services/export_service.py +577 -0
  52. ebk/services/import_service.py +447 -0
  53. ebk/services/personal_metadata_service.py +347 -0
  54. ebk/services/queue_service.py +253 -0
  55. ebk/services/tag_service.py +281 -0
  56. ebk/services/text_extraction.py +317 -0
  57. ebk/services/view_service.py +12 -0
  58. ebk/similarity/__init__.py +77 -0
  59. ebk/similarity/base.py +154 -0
  60. ebk/similarity/core.py +471 -0
  61. ebk/similarity/extractors.py +168 -0
  62. ebk/similarity/metrics.py +376 -0
  63. ebk/skills/SKILL.md +182 -0
  64. ebk/skills/__init__.py +1 -0
  65. ebk/vfs/__init__.py +101 -0
  66. ebk/vfs/base.py +298 -0
  67. ebk/vfs/library_vfs.py +122 -0
  68. ebk/vfs/nodes/__init__.py +54 -0
  69. ebk/vfs/nodes/authors.py +196 -0
  70. ebk/vfs/nodes/books.py +480 -0
  71. ebk/vfs/nodes/files.py +155 -0
  72. ebk/vfs/nodes/metadata.py +385 -0
  73. ebk/vfs/nodes/root.py +100 -0
  74. ebk/vfs/nodes/similar.py +165 -0
  75. ebk/vfs/nodes/subjects.py +184 -0
  76. ebk/vfs/nodes/tags.py +371 -0
  77. ebk/vfs/resolver.py +228 -0
  78. ebk/vfs_router.py +275 -0
  79. ebk/views/__init__.py +32 -0
  80. ebk/views/dsl.py +668 -0
  81. ebk/views/service.py +619 -0
  82. ebk-0.4.4.dist-info/METADATA +755 -0
  83. ebk-0.4.4.dist-info/RECORD +87 -0
  84. ebk-0.4.4.dist-info/WHEEL +5 -0
  85. ebk-0.4.4.dist-info/entry_points.txt +2 -0
  86. ebk-0.4.4.dist-info/licenses/LICENSE +21 -0
  87. ebk-0.4.4.dist-info/top_level.txt +1 -0
ebk/skills/SKILL.md ADDED
@@ -0,0 +1,182 @@
1
+ ---
2
+ name: ebk
3
+ description: Use this skill when working with ebk - an eBook metadata management CLI with SQLite storage, full-text search, hierarchical tags, and a virtual filesystem shell. Invoke for library management, book operations, imports/exports, or ebk shell navigation.
4
+ ---
5
+
6
+ # ebk - eBook Metadata Management
7
+
8
+ A CLI tool for managing ebook libraries with SQLite storage, full-text search, and a virtual filesystem shell.
9
+
10
+ ## Quick Reference
11
+
12
+ ```bash
13
+ # Library management
14
+ ebk lib init ~/my-library
15
+ ebk lib migrate
16
+ ebk lib backup -o backup.tar.gz
17
+ ebk lib check
18
+
19
+ # Query and discovery
20
+ ebk query search "python programming"
21
+ ebk query list --author "Knuth"
22
+ ebk query stats
23
+ ebk query sql "SELECT title FROM books WHERE language='en'"
24
+
25
+ # Import books
26
+ ebk import add book.pdf
27
+ ebk import folder ~/Downloads/books/
28
+ ebk import calibre ~/Calibre\ Library/
29
+ ebk import isbn 978-0134685991
30
+
31
+ # Export library
32
+ ebk export json -o library.json
33
+ ebk export csv -o library.csv
34
+ ebk export opds -o catalog.xml --copy-files
35
+ ebk export goodreads -o goodreads.csv
36
+ ebk export calibre -o calibre.csv
37
+
38
+ # Book operations
39
+ ebk book info 42
40
+ ebk book read 42 --text
41
+ ebk book rate 42 --rating 5
42
+ ebk book tag 42 --add "Work/Project-2024"
43
+ ebk book purge ~/library --no-files --execute
44
+
45
+ # Interactive
46
+ ebk shell # VFS shell
47
+ ebk serve # Web server
48
+ ```
49
+
50
+ ## Command Groups
51
+
52
+ | Command | Purpose |
53
+ |---------|---------|
54
+ | `ebk lib` | Library management (init, migrate, backup, restore, check) |
55
+ | `ebk query` | Query and discovery (search, list, stats, sql) |
56
+ | `ebk import` | Import books (add, folder, calibre, isbn, opds, url) |
57
+ | `ebk export` | Export library (json, csv, html, opds, goodreads, calibre) |
58
+ | `ebk book` | Book operations (info, read, rate, favorite, tag, purge, merge) |
59
+ | `ebk note` | Manage annotations (add, list, extract, export) |
60
+ | `ebk tag` | Manage hierarchical tags (list, tree, add, remove, rename) |
61
+ | `ebk queue` | Reading queue (list, add, remove, move, next) |
62
+ | `ebk view` | Named library subsets (create, list, show, delete, edit) |
63
+ | `ebk skill` | Claude Code skill management |
64
+
65
+ ## Search Syntax
66
+
67
+ ebk supports advanced query syntax:
68
+
69
+ ```bash
70
+ # Field-specific search
71
+ ebk query search "title:Python author:Knuth"
72
+
73
+ # Boolean operators
74
+ ebk query search "python AND programming"
75
+ ebk query search "python OR ruby"
76
+ ebk query search "NOT java"
77
+
78
+ # Exact phrases
79
+ ebk query search '"machine learning"'
80
+
81
+ # Comparisons
82
+ ebk query search "rating:>=4"
83
+ ebk query search "pages:>500"
84
+ ```
85
+
86
+ ## VFS Shell
87
+
88
+ `ebk shell` provides filesystem-like navigation:
89
+
90
+ ```
91
+ / # Root
92
+ /books/{id}/ # Book by ID
93
+ /books/{id}/title # Title as text
94
+ /books/{id}/authors # Authors list
95
+ /books/{id}/metadata # Full JSON metadata
96
+ /books/{id}/files/ # Ebook files
97
+ /books/{id}/similar/ # Similar books
98
+ /authors/{name}/ # Browse by author
99
+ /subjects/{subject}/ # Browse by subject
100
+ /tags/{hierarchy}/ # Hierarchical tags
101
+ ```
102
+
103
+ Shell commands: `ls`, `cd`, `cat`, `find`, `grep`, `ln`, `mv`, `rm`, `mkdir`
104
+
105
+ Supports piping: `ls /books/ | grep Python | head 10`
106
+
107
+ ## Python API
108
+
109
+ ```python
110
+ from ebk.library_db import Library
111
+
112
+ # Open library
113
+ lib = Library.open("~/my-library")
114
+
115
+ # Fluent query API
116
+ results = (lib.query()
117
+ .filter_by_author("Knuth")
118
+ .filter_by_language("en")
119
+ .order_by("title")
120
+ .limit(20)
121
+ .all())
122
+
123
+ # Search
124
+ books = lib.search("python programming", limit=10)
125
+
126
+ # Get book
127
+ book = lib.get_book(42)
128
+ print(book.title, book.authors)
129
+
130
+ lib.close()
131
+ ```
132
+
133
+ ## Configuration
134
+
135
+ Config file: `~/.config/ebk/config.json`
136
+
137
+ ```bash
138
+ ebk config --library-path ~/my-library # Set default library
139
+ ebk config --llm-provider ollama # Set LLM provider
140
+ ebk config --show # Show current config
141
+ ```
142
+
143
+ ## Database Schema
144
+
145
+ Core tables: `books`, `authors`, `subjects`, `files`, `covers`, `tags`, `personal_metadata`, `annotations`, `books_fts` (FTS5)
146
+
147
+ Library directory structure:
148
+ ```
149
+ library/
150
+ ├── library.db # SQLite database
151
+ ├── files/ # Hash-prefixed ebook storage
152
+ └── covers/
153
+ └── thumbnails/ # Cover images
154
+ ```
155
+
156
+ ## Views DSL
157
+
158
+ Views are named, composable library subsets:
159
+
160
+ ```yaml
161
+ name: unread-python
162
+ description: Unread Python books
163
+ filter:
164
+ reading_status: unread
165
+ subjects:
166
+ any: ["Python", "Programming"]
167
+ sort: added desc
168
+ limit: 50
169
+ ```
170
+
171
+ ```bash
172
+ ebk view create unread-python --filter "reading_status:unread" --filter "subject:Python"
173
+ ebk query list --view unread-python
174
+ ```
175
+
176
+ ## Tips
177
+
178
+ 1. Use `ebk config --library-path` to set a default library
179
+ 2. `ebk shell` for interactive exploration
180
+ 3. `ebk query sql` for complex custom queries
181
+ 4. `ebk export opds` for Android reader compatibility
182
+ 5. Tags are hierarchical: `Work/Project-2024/Research`
ebk/skills/__init__.py ADDED
@@ -0,0 +1 @@
1
+ # Claude Code skill data for ebk
ebk/vfs/__init__.py ADDED
@@ -0,0 +1,101 @@
1
+ """Virtual File System for navigating the library database.
2
+
3
+ The VFS provides a filesystem-like interface for browsing and interacting
4
+ with the ebook library. It maps database entities to a hierarchical structure
5
+ that can be navigated with familiar shell commands.
6
+
7
+ Architecture:
8
+
9
+ ```
10
+ / # Root (RootNode)
11
+ ├── books/ # All books (BooksDirectoryNode)
12
+ │ ├── 1/ # Book 1 (BookNode)
13
+ │ │ ├── title # Metadata file (TitleFileNode)
14
+ │ │ ├── authors # Metadata file (AuthorsFileNode)
15
+ │ │ ├── description # Metadata file
16
+ │ │ ├── text # Extracted text (TextFileNode)
17
+ │ │ ├── files/ # Physical files (FilesDirectoryNode)
18
+ │ │ │ ├── book.pdf
19
+ │ │ │ └── book.epub
20
+ │ │ ├── similar/ # Similar books (SimilarDirectoryNode)
21
+ │ │ ├── annotations/ # User annotations
22
+ │ │ └── covers/ # Cover images
23
+ │ └── 2/
24
+ ├── authors/ # Browse by author (AuthorsDirectoryNode)
25
+ │ └── knuth-donald/ # Books by this author
26
+ ├── subjects/ # Browse by subject
27
+ └── series/ # Browse by series
28
+ ```
29
+
30
+ Node Types:
31
+
32
+ - Node: Base class for all VFS entries
33
+ - DirectoryNode: Can contain children (cd into them)
34
+ - FileNode: Leaf nodes with content (cat them)
35
+ - VirtualNode: Dynamically computed (e.g., /books/, /similar/)
36
+ - SymlinkNode: Links to other nodes
37
+
38
+ Path Resolution:
39
+
40
+ The PathResolver handles navigation:
41
+ - Absolute paths: /books/42/title
42
+ - Relative paths: ../other, ./files
43
+ - Special: ., .., ~ (home = /)
44
+ - Symlink following
45
+ - Tab completion support
46
+
47
+ Usage Example:
48
+
49
+ ```python
50
+ from ebk.library_db import Library
51
+ from ebk.vfs import LibraryVFS
52
+
53
+ # Create VFS for a library
54
+ lib = Library.open("/path/to/library")
55
+ vfs = LibraryVFS(lib)
56
+
57
+ # Navigate
58
+ root = vfs.root
59
+ books_dir = vfs.resolver.resolve("/books", root)
60
+ book_node = vfs.resolver.resolve("/books/42", root)
61
+
62
+ # List children
63
+ children = books_dir.list_children() # All books
64
+ for child in children:
65
+ print(child.name, child.get_info())
66
+
67
+ # Read file content
68
+ title_node = vfs.resolver.resolve("/books/42/title", root)
69
+ if isinstance(title_node, FileNode):
70
+ content = title_node.read_content()
71
+ print(content)
72
+ ```
73
+ """
74
+
75
+ from ebk.vfs.base import (
76
+ Node,
77
+ DirectoryNode,
78
+ FileNode,
79
+ VirtualNode,
80
+ SymlinkNode,
81
+ NodeType,
82
+ )
83
+ from ebk.vfs.resolver import PathResolver, PathError, NotADirectoryError, NotFoundError
84
+ from ebk.vfs.library_vfs import LibraryVFS
85
+
86
+ __all__ = [
87
+ # Main entry point
88
+ "LibraryVFS",
89
+ # Core classes
90
+ "Node",
91
+ "DirectoryNode",
92
+ "FileNode",
93
+ "VirtualNode",
94
+ "SymlinkNode",
95
+ "NodeType",
96
+ # Path resolution
97
+ "PathResolver",
98
+ "PathError",
99
+ "NotADirectoryError",
100
+ "NotFoundError",
101
+ ]
ebk/vfs/base.py ADDED
@@ -0,0 +1,298 @@
1
+ """Base classes for the Virtual File System.
2
+
3
+ The VFS maps the library database to a filesystem-like structure
4
+ that can be navigated with shell commands (cd, ls, cat, etc.).
5
+
6
+ Architecture:
7
+ - Node: Base class for all VFS nodes
8
+ - DirectoryNode: Nodes that can contain children (cd into them)
9
+ - FileNode: Leaf nodes with content (cat them)
10
+ - VirtualNode: Dynamically computed nodes (e.g., similar books)
11
+ """
12
+
13
+ from abc import ABC, abstractmethod
14
+ from enum import Enum
15
+ from typing import Dict, List, Optional, Any
16
+
17
+
18
+ class NodeType(Enum):
19
+ """Type of VFS node."""
20
+ DIRECTORY = "directory"
21
+ FILE = "file"
22
+ VIRTUAL = "virtual"
23
+ SYMLINK = "symlink"
24
+
25
+
26
+ class Node(ABC):
27
+ """Base class for all VFS nodes.
28
+
29
+ A Node represents an entry in the virtual filesystem. It can be
30
+ a directory (navigable), a file (readable), or something virtual
31
+ (dynamically computed).
32
+
33
+ Attributes:
34
+ name: The name of this node (e.g., "title", "books", "42")
35
+ parent: Parent directory node (None for root)
36
+ node_type: Type of node (directory, file, virtual, symlink)
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ name: str,
42
+ parent: Optional['DirectoryNode'] = None,
43
+ node_type: NodeType = NodeType.FILE,
44
+ ):
45
+ """Initialize a VFS node.
46
+
47
+ Args:
48
+ name: Name of this node
49
+ parent: Parent directory (None for root)
50
+ node_type: Type of node
51
+ """
52
+ self.name = name
53
+ self.parent = parent
54
+ self.node_type = node_type
55
+
56
+ @abstractmethod
57
+ def get_info(self) -> Dict[str, Any]:
58
+ """Get metadata about this node for display.
59
+
60
+ Returns:
61
+ Dict with keys like: size, modified, type, description
62
+ """
63
+ pass
64
+
65
+ def get_path(self) -> str:
66
+ """Get absolute path to this node.
67
+
68
+ Returns:
69
+ Path like /books/42/title
70
+ """
71
+ if self.parent is None:
72
+ return "/"
73
+
74
+ parts = []
75
+ node = self
76
+ while node.parent is not None:
77
+ parts.append(node.name)
78
+ node = node.parent
79
+
80
+ if not parts:
81
+ return "/"
82
+
83
+ return "/" + "/".join(reversed(parts))
84
+
85
+ def __repr__(self) -> str:
86
+ return f"{self.__class__.__name__}(name='{self.name}', path='{self.get_path()}')"
87
+
88
+
89
+ class DirectoryNode(Node):
90
+ """A directory node that can contain children.
91
+
92
+ Directory nodes can be navigated into with `cd` and their
93
+ children can be listed with `ls`.
94
+
95
+ Children can be:
96
+ - Static: Fixed set of children
97
+ - Dynamic: Children computed on-demand (e.g., book list from DB)
98
+ """
99
+
100
+ def __init__(self, name: str, parent: Optional['DirectoryNode'] = None):
101
+ """Initialize a directory node.
102
+
103
+ Args:
104
+ name: Name of this directory
105
+ parent: Parent directory
106
+ """
107
+ super().__init__(name, parent, NodeType.DIRECTORY)
108
+ self._children: Dict[str, Node] = {}
109
+
110
+ @abstractmethod
111
+ def list_children(self) -> List[Node]:
112
+ """List all children of this directory.
113
+
114
+ This may compute children dynamically from the database.
115
+
116
+ Returns:
117
+ List of child nodes
118
+ """
119
+ pass
120
+
121
+ @abstractmethod
122
+ def get_child(self, name: str) -> Optional[Node]:
123
+ """Get a child node by name.
124
+
125
+ Args:
126
+ name: Name of child node
127
+
128
+ Returns:
129
+ Child node or None if not found
130
+ """
131
+ pass
132
+
133
+ def get_info(self) -> Dict[str, Any]:
134
+ """Get directory metadata.
135
+
136
+ Returns:
137
+ Dict with directory information
138
+ """
139
+ children = self.list_children()
140
+ return {
141
+ "type": "directory",
142
+ "name": self.name,
143
+ "children_count": len(children),
144
+ "path": self.get_path(),
145
+ }
146
+
147
+
148
+ class FileNode(Node):
149
+ """A file node with readable content.
150
+
151
+ File nodes represent data that can be read with `cat`.
152
+ Examples: book title, description, full text, etc.
153
+ """
154
+
155
+ def __init__(
156
+ self,
157
+ name: str,
158
+ parent: Optional[DirectoryNode] = None,
159
+ size: Optional[int] = None,
160
+ ):
161
+ """Initialize a file node.
162
+
163
+ Args:
164
+ name: Name of this file
165
+ parent: Parent directory
166
+ size: Size in bytes (if known)
167
+ """
168
+ super().__init__(name, parent, NodeType.FILE)
169
+ self._size = size
170
+
171
+ @abstractmethod
172
+ def read_content(self) -> str:
173
+ """Read the content of this file.
174
+
175
+ Returns:
176
+ File content as string
177
+ """
178
+ pass
179
+
180
+ def write_content(self, content: str) -> None:
181
+ """Write content to this file.
182
+
183
+ Args:
184
+ content: Content to write
185
+
186
+ Raises:
187
+ NotImplementedError: If the file is read-only
188
+ """
189
+ raise NotImplementedError(f"File '{self.name}' is read-only")
190
+
191
+ def is_writable(self) -> bool:
192
+ """Check if this file is writable.
193
+
194
+ Returns:
195
+ True if file supports writing, False otherwise
196
+ """
197
+ # By default, files are read-only unless they override write_content
198
+ try:
199
+ # Try calling write_content with empty string to see if it raises NotImplementedError
200
+ # This is a bit hacky but works
201
+ return hasattr(self.__class__, 'write_content') and \
202
+ self.__class__.write_content != FileNode.write_content
203
+ except Exception:
204
+ return False
205
+
206
+ def get_info(self) -> Dict[str, Any]:
207
+ """Get file metadata.
208
+
209
+ Returns:
210
+ Dict with file information
211
+ """
212
+ return {
213
+ "type": "file",
214
+ "name": self.name,
215
+ "size": self._size,
216
+ "path": self.get_path(),
217
+ "writable": self.is_writable(),
218
+ }
219
+
220
+
221
+ class VirtualNode(DirectoryNode):
222
+ """A virtual directory with dynamically computed children.
223
+
224
+ Virtual nodes don't have a fixed set of children - they compute
225
+ them on-demand from the database or other sources.
226
+
227
+ Examples:
228
+ - /books/ - Lists all books from DB
229
+ - /books/42/similar/ - Computes similar books on-demand
230
+ - /authors/ - Lists all authors from DB
231
+ """
232
+
233
+ def __init__(self, name: str, parent: Optional[DirectoryNode] = None):
234
+ """Initialize a virtual directory node.
235
+
236
+ Args:
237
+ name: Name of this directory
238
+ parent: Parent directory
239
+ """
240
+ super().__init__(name, parent)
241
+ self.node_type = NodeType.VIRTUAL
242
+
243
+ def get_info(self) -> Dict[str, Any]:
244
+ """Get virtual directory metadata.
245
+
246
+ Returns:
247
+ Dict with virtual directory information
248
+ """
249
+ info = super().get_info()
250
+ info["type"] = "virtual"
251
+ return info
252
+
253
+
254
+ class SymlinkNode(Node):
255
+ """A symbolic link pointing to another node.
256
+
257
+ Used for creating convenient shortcuts, like similar books
258
+ appearing as links in /books/42/similar/.
259
+
260
+ Attributes:
261
+ target_path: Path to the target node
262
+ metadata: Optional metadata dict to include in get_info()
263
+ """
264
+
265
+ def __init__(
266
+ self,
267
+ name: str,
268
+ target_path: str,
269
+ parent: Optional[DirectoryNode] = None,
270
+ metadata: Optional[Dict[str, Any]] = None,
271
+ ):
272
+ """Initialize a symlink node.
273
+
274
+ Args:
275
+ name: Name of this symlink
276
+ target_path: Path to target node
277
+ parent: Parent directory
278
+ metadata: Optional metadata to include in get_info()
279
+ """
280
+ super().__init__(name, parent, NodeType.SYMLINK)
281
+ self.target_path = target_path
282
+ self.metadata = metadata or {}
283
+
284
+ def get_info(self) -> Dict[str, Any]:
285
+ """Get symlink metadata.
286
+
287
+ Returns:
288
+ Dict with symlink information plus any provided metadata
289
+ """
290
+ info = {
291
+ "type": "symlink",
292
+ "name": self.name,
293
+ "target": self.target_path,
294
+ "path": self.get_path(),
295
+ }
296
+ # Merge in any provided metadata
297
+ info.update(self.metadata)
298
+ return info
ebk/vfs/library_vfs.py ADDED
@@ -0,0 +1,122 @@
1
+ """Main LibraryVFS class - entry point for VFS access."""
2
+
3
+ from ebk.library_db import Library
4
+ from ebk.vfs.base import DirectoryNode, Node
5
+ from ebk.vfs.resolver import PathResolver
6
+ from ebk.vfs.nodes import RootNode
7
+
8
+
9
+ class LibraryVFS:
10
+ """Virtual File System for a library.
11
+
12
+ This is the main entry point for accessing the VFS. It creates
13
+ the root node and provides a resolver for path navigation.
14
+
15
+ Usage:
16
+ >>> lib = Library.open("/path/to/library")
17
+ >>> vfs = LibraryVFS(lib)
18
+ >>>
19
+ >>> # Navigate using resolver
20
+ >>> books_dir = vfs.resolver.resolve("/books", vfs.root)
21
+ >>> book_node = vfs.resolver.resolve("/books/42", vfs.root)
22
+ >>>
23
+ >>> # List children
24
+ >>> children = books_dir.list_children()
25
+ >>>
26
+ >>> # Read file content
27
+ >>> title_node = vfs.resolver.resolve("/books/42/title", vfs.root)
28
+ >>> if isinstance(title_node, FileNode):
29
+ >>> print(title_node.read_content())
30
+ """
31
+
32
+ def __init__(self, library: Library):
33
+ """Initialize VFS for a library.
34
+
35
+ Args:
36
+ library: Library instance
37
+ """
38
+ self.library = library
39
+ self.root = RootNode(library)
40
+ self.resolver = PathResolver(self.root)
41
+ self.current = self.root # Current working directory
42
+
43
+ def cd(self, path: str) -> bool:
44
+ """Change current directory.
45
+
46
+ Args:
47
+ path: Path to navigate to
48
+
49
+ Returns:
50
+ True if successful, False otherwise
51
+ """
52
+ new_dir = self.resolver.resolve_directory(path, self.current)
53
+ if new_dir is None:
54
+ return False
55
+
56
+ self.current = new_dir
57
+ return True
58
+
59
+ def pwd(self) -> str:
60
+ """Get current working directory path.
61
+
62
+ Returns:
63
+ Current path
64
+ """
65
+ return self.current.get_path()
66
+
67
+ def ls(self, path: str = ".") -> list:
68
+ """List children of a directory.
69
+
70
+ Args:
71
+ path: Path to list (default: current directory)
72
+
73
+ Returns:
74
+ List of nodes
75
+ """
76
+ node = self.resolver.resolve(path, self.current)
77
+ if node is None or not isinstance(node, DirectoryNode):
78
+ return []
79
+
80
+ return node.list_children()
81
+
82
+ def cat(self, path: str) -> str:
83
+ """Read content of a file node.
84
+
85
+ Args:
86
+ path: Path to file
87
+
88
+ Returns:
89
+ File content or error message
90
+ """
91
+ from ebk.vfs.base import FileNode
92
+
93
+ node = self.resolver.resolve(path, self.current)
94
+ if node is None:
95
+ return f"cat: {path}: No such file or directory"
96
+
97
+ if not isinstance(node, FileNode):
98
+ return f"cat: {path}: Is a directory"
99
+
100
+ return node.read_content()
101
+
102
+ def get_node(self, path: str) -> Node:
103
+ """Resolve a path to a node.
104
+
105
+ Args:
106
+ path: Path to resolve
107
+
108
+ Returns:
109
+ Resolved node or None
110
+ """
111
+ return self.resolver.resolve(path, self.current)
112
+
113
+ def complete(self, partial: str) -> list:
114
+ """Get tab completion candidates.
115
+
116
+ Args:
117
+ partial: Partial path
118
+
119
+ Returns:
120
+ List of completion candidates
121
+ """
122
+ return self.resolver.complete_path(partial, self.current)