vibefs 0.1.0__py3-none-any.whl → 0.2.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.
- {vibefs-0.1.0.dist-info → vibefs-0.2.0.dist-info}/METADATA +4 -2
- vibefs-0.2.0.dist-info/RECORD +6 -0
- vibefs.py +54 -14
- vibefs-0.1.0.dist-info/RECORD +0 -6
- {vibefs-0.1.0.dist-info → vibefs-0.2.0.dist-info}/WHEEL +0 -0
- {vibefs-0.1.0.dist-info → vibefs-0.2.0.dist-info}/entry_points.txt +0 -0
- {vibefs-0.1.0.dist-info → vibefs-0.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vibefs
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: A simple, secure file preview service with time-limited URLs
|
|
5
5
|
Project-URL: Homepage, https://github.com/reorx/vibefs
|
|
6
6
|
Project-URL: Repository, https://github.com/reorx/vibefs
|
|
@@ -47,7 +47,9 @@ uvx vibefs --help
|
|
|
47
47
|
vibefs allow /path/to/file.py
|
|
48
48
|
# http://localhost:17173/f/a3b7c2d1/file.py
|
|
49
49
|
|
|
50
|
-
vibefs allow /path/to/file.py --ttl 300
|
|
50
|
+
vibefs allow /path/to/file.py --ttl 300 # 5 minutes
|
|
51
|
+
vibefs allow /path/to/file.py --head 50 # Only first 50 lines
|
|
52
|
+
vibefs allow /path/to/file.py --tail 20 # Only last 20 lines
|
|
51
53
|
```
|
|
52
54
|
|
|
53
55
|
The daemon starts automatically if it's not already running.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
vibefs.py,sha256=CGK0gpekvvtS3AU-vcQ0-W-PEq-69Fq_Bcj7Ruonaqg,16943
|
|
2
|
+
vibefs-0.2.0.dist-info/METADATA,sha256=7YOdTKiePBMyZqMH9ydfA2nusIJMNkNoRDPhyKrPe1c,4257
|
|
3
|
+
vibefs-0.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
4
|
+
vibefs-0.2.0.dist-info/entry_points.txt,sha256=6PJSQXVzolnyMPQLeX3gC9RXGms6kDPmkJhKcvi-SSw,38
|
|
5
|
+
vibefs-0.2.0.dist-info/licenses/LICENSE,sha256=3FpISEUWFm2YLTutnpMlXsfK8WGHarh6uaNARMxikcc,1062
|
|
6
|
+
vibefs-0.2.0.dist-info/RECORD,,
|
vibefs.py
CHANGED
|
@@ -249,11 +249,19 @@ def start_cleanup_timer():
|
|
|
249
249
|
class BaseRenderer:
|
|
250
250
|
"""Default renderer: returns raw file content with guessed content-type."""
|
|
251
251
|
|
|
252
|
-
def render(self, filepath):
|
|
252
|
+
def render(self, filepath, head=None, tail=None):
|
|
253
253
|
content_type, _ = mimetypes.guess_type(filepath)
|
|
254
254
|
if content_type is None:
|
|
255
255
|
content_type = 'application/octet-stream'
|
|
256
256
|
bottle.response.content_type = content_type
|
|
257
|
+
if head is not None or tail is not None:
|
|
258
|
+
with open(filepath) as f:
|
|
259
|
+
lines = f.readlines()
|
|
260
|
+
if head is not None:
|
|
261
|
+
lines = lines[:head]
|
|
262
|
+
elif tail is not None:
|
|
263
|
+
lines = lines[-tail:]
|
|
264
|
+
return ''.join(lines)
|
|
257
265
|
with open(filepath, 'rb') as f:
|
|
258
266
|
return f.read()
|
|
259
267
|
|
|
@@ -261,13 +269,19 @@ class BaseRenderer:
|
|
|
261
269
|
class CodeRenderer:
|
|
262
270
|
"""Renders code files with syntax highlighting via Pygments."""
|
|
263
271
|
|
|
264
|
-
def render(self, filepath):
|
|
272
|
+
def render(self, filepath, head=None, tail=None):
|
|
265
273
|
from pygments import highlight
|
|
266
274
|
from pygments.formatters import HtmlFormatter
|
|
267
275
|
from pygments.lexers import get_lexer_for_filename, TextLexer
|
|
268
276
|
|
|
269
277
|
with open(filepath) as f:
|
|
270
|
-
|
|
278
|
+
lines = f.readlines()
|
|
279
|
+
|
|
280
|
+
if head is not None:
|
|
281
|
+
lines = lines[:head]
|
|
282
|
+
elif tail is not None:
|
|
283
|
+
lines = lines[-tail:]
|
|
284
|
+
code = ''.join(lines)
|
|
271
285
|
|
|
272
286
|
try:
|
|
273
287
|
lexer = get_lexer_for_filename(filepath)
|
|
@@ -281,20 +295,28 @@ class CodeRenderer:
|
|
|
281
295
|
)
|
|
282
296
|
highlighted = highlight(code, lexer, formatter)
|
|
283
297
|
css = formatter.get_style_defs('.highlight')
|
|
284
|
-
|
|
298
|
+
display_path = _display_path(filepath)
|
|
285
299
|
stat = os.stat(filepath)
|
|
286
300
|
file_size = _format_size(stat.st_size)
|
|
287
301
|
file_mtime = time.strftime('%Y-%m-%d %H:%M', time.localtime(stat.st_mtime))
|
|
302
|
+
file_ctime = time.strftime('%Y-%m-%d %H:%M', time.localtime(stat.st_birthtime if hasattr(stat, 'st_birthtime') else stat.st_ctime))
|
|
288
303
|
|
|
289
304
|
bottle.response.content_type = 'text/html; charset=utf-8'
|
|
290
305
|
return CODE_HTML_TEMPLATE.format(
|
|
291
|
-
|
|
292
|
-
|
|
306
|
+
display_path=display_path,
|
|
307
|
+
file_meta=f'{file_size} · {file_mtime} (mtime) · {file_ctime} (ctime)',
|
|
293
308
|
pygments_css=css,
|
|
294
309
|
highlighted=highlighted,
|
|
295
310
|
)
|
|
296
311
|
|
|
297
312
|
|
|
313
|
+
def _display_path(filepath):
|
|
314
|
+
home = os.path.expanduser('~')
|
|
315
|
+
if filepath.startswith(home + '/'):
|
|
316
|
+
return '~/' + filepath[len(home) + 1:]
|
|
317
|
+
return filepath
|
|
318
|
+
|
|
319
|
+
|
|
298
320
|
def _format_size(nbytes):
|
|
299
321
|
for unit in ('B', 'KB', 'MB', 'GB'):
|
|
300
322
|
if nbytes < 1024:
|
|
@@ -308,7 +330,7 @@ CODE_HTML_TEMPLATE = """<!DOCTYPE html>
|
|
|
308
330
|
<head>
|
|
309
331
|
<meta charset="utf-8">
|
|
310
332
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
311
|
-
<title>{
|
|
333
|
+
<title>{display_path}</title>
|
|
312
334
|
<style>
|
|
313
335
|
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
|
314
336
|
body {{
|
|
@@ -321,17 +343,18 @@ CODE_HTML_TEMPLATE = """<!DOCTYPE html>
|
|
|
321
343
|
background: #2d2d2d;
|
|
322
344
|
border-bottom: 1px solid #404040;
|
|
323
345
|
padding: 12px 16px;
|
|
346
|
+
}}
|
|
347
|
+
.file-path {{
|
|
324
348
|
font-size: 14px;
|
|
325
349
|
font-weight: 600;
|
|
326
350
|
color: #e0e0e0;
|
|
327
|
-
|
|
328
|
-
justify-content: space-between;
|
|
329
|
-
align-items: center;
|
|
351
|
+
word-break: break-all;
|
|
330
352
|
}}
|
|
331
|
-
.file-
|
|
353
|
+
.file-meta {{
|
|
332
354
|
font-size: 12px;
|
|
333
355
|
font-weight: 400;
|
|
334
356
|
color: #888;
|
|
357
|
+
margin-top: 4px;
|
|
335
358
|
}}
|
|
336
359
|
.file-content {{
|
|
337
360
|
overflow-x: auto;
|
|
@@ -367,7 +390,10 @@ CODE_HTML_TEMPLATE = """<!DOCTYPE html>
|
|
|
367
390
|
</style>
|
|
368
391
|
</head>
|
|
369
392
|
<body>
|
|
370
|
-
<div class="file-header"
|
|
393
|
+
<div class="file-header">
|
|
394
|
+
<div class="file-path">{display_path}</div>
|
|
395
|
+
<div class="file-meta">{file_meta}</div>
|
|
396
|
+
</div>
|
|
371
397
|
<div class="file-content">
|
|
372
398
|
{highlighted}
|
|
373
399
|
</div>
|
|
@@ -429,8 +455,13 @@ def serve_file(token, filename):
|
|
|
429
455
|
if not os.path.isfile(filepath):
|
|
430
456
|
bottle.abort(404, 'File no longer exists on disk')
|
|
431
457
|
|
|
458
|
+
head = bottle.request.query.get('head')
|
|
459
|
+
tail = bottle.request.query.get('tail')
|
|
460
|
+
head = int(head) if head else None
|
|
461
|
+
tail = int(tail) if tail else None
|
|
462
|
+
|
|
432
463
|
renderer = get_renderer(filepath)
|
|
433
|
-
return renderer.render(filepath)
|
|
464
|
+
return renderer.render(filepath, head=head, tail=tail)
|
|
434
465
|
|
|
435
466
|
|
|
436
467
|
EXPIRED_TEMPLATE = """<!DOCTYPE html>
|
|
@@ -484,7 +515,9 @@ def serve(port, host, foreground):
|
|
|
484
515
|
@click.option('--ttl', default=DEFAULT_TTL, show_default=True, help='Time-to-live in seconds')
|
|
485
516
|
@click.option('--port', default=DEFAULT_PORT, show_default=True, help='Port for URL generation')
|
|
486
517
|
@click.option('--host', default='localhost', show_default=True, help='Host for URL generation')
|
|
487
|
-
|
|
518
|
+
@click.option('--head', default=None, type=int, help='Only show first N lines')
|
|
519
|
+
@click.option('--tail', default=None, type=int, help='Only show last N lines')
|
|
520
|
+
def allow(path, ttl, port, host, head, tail):
|
|
488
521
|
"""Authorize a file for access, auto-start daemon if needed, and print its URL."""
|
|
489
522
|
ensure_state_dir()
|
|
490
523
|
token, filename = add_authorization(path, ttl)
|
|
@@ -493,6 +526,13 @@ def allow(path, ttl, port, host):
|
|
|
493
526
|
url = f'{base_url.rstrip("/")}/f/{token}/{filename}'
|
|
494
527
|
else:
|
|
495
528
|
url = f'http://{host}:{port}/f/{token}/{filename}'
|
|
529
|
+
params = []
|
|
530
|
+
if head is not None:
|
|
531
|
+
params.append(f'head={head}')
|
|
532
|
+
if tail is not None:
|
|
533
|
+
params.append(f'tail={tail}')
|
|
534
|
+
if params:
|
|
535
|
+
url += '?' + '&'.join(params)
|
|
496
536
|
click.echo(url)
|
|
497
537
|
|
|
498
538
|
# Auto-start daemon if not running
|
vibefs-0.1.0.dist-info/RECORD
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
vibefs.py,sha256=EUF-b92So13rHGc_-aGLqz12mP9SwcNr_A63KCto3Io,15500
|
|
2
|
-
vibefs-0.1.0.dist-info/METADATA,sha256=KA9NR5IuyYqqMONI0JxuP7cssqw87UB1JwVfsGzKIKo,4129
|
|
3
|
-
vibefs-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
4
|
-
vibefs-0.1.0.dist-info/entry_points.txt,sha256=6PJSQXVzolnyMPQLeX3gC9RXGms6kDPmkJhKcvi-SSw,38
|
|
5
|
-
vibefs-0.1.0.dist-info/licenses/LICENSE,sha256=3FpISEUWFm2YLTutnpMlXsfK8WGHarh6uaNARMxikcc,1062
|
|
6
|
-
vibefs-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|