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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vibefs
3
- Version: 0.1.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 # 5 minutes
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
- code = f.read()
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
- filename = os.path.basename(filepath)
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
- filename=filename,
292
- file_info=f'{file_size} · {file_mtime}',
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>{filename}</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
- display: flex;
328
- justify-content: space-between;
329
- align-items: center;
351
+ word-break: break-all;
330
352
  }}
331
- .file-info {{
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"><span>{filename}</span><span class="file-info">{file_info}</span></div>
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
- def allow(path, ttl, port, host):
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
@@ -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