|
95 | 95 | "name": "stdout", |
96 | 96 | "output_type": "stream", |
97 | 97 | "text": [ |
98 | | - "\u001b[34m__pycache__\u001b[m\u001b[m\n", |
99 | | - "_parallel_win.ipynb\n", |
100 | | - "_quarto.yml\n", |
101 | | - "00_test.ipynb\n", |
102 | 98 | "000_tour.ipynb\n", |
| 99 | + "00_test.ipynb\n", |
103 | 100 | "01_basics.ipynb\n", |
104 | 101 | "02_foundation.ipynb\n", |
105 | | - "03_xtras\n" |
| 102 | + "03_xtras.ipynb\n", |
| 103 | + "03a_parallel.ipynb\n", |
| 104 | + "03b_net.ipynb\n", |
| 105 | + "04_docments.ipy\n" |
106 | 106 | ] |
107 | 107 | } |
108 | 108 | ], |
|
128 | 128 | "name": "stdout", |
129 | 129 | "output_type": "stream", |
130 | 130 | "text": [ |
131 | | - "ls: f*: No such file or directory\n", |
| 131 | + "ls: cannot access 'f*': No such file or directory\n", |
132 | 132 | "\n" |
133 | 133 | ] |
134 | 134 | } |
|
375 | 375 | "outputs": [], |
376 | 376 | "source": [ |
377 | 377 | "#| export\n", |
| 378 | + "def valid_path(path:str, must_exist:bool=True) -> Path:\n", |
| 379 | + " 'Return expanded/resolved Path, raising FileNotFoundError if must_exist and missing'\n", |
| 380 | + " p = Path(path).expanduser().resolve()\n", |
| 381 | + " if must_exist and not p.exists(): raise FileNotFoundError(f'File not found: {p}')\n", |
| 382 | + " return p\n", |
| 383 | + "\n", |
378 | 384 | "def view(\n", |
379 | 385 | " path:str, # Path to directory or file to view\n", |
380 | 386 | " view_range:tuple[int,int]=None, # Optional 1-indexed (start, end) line range for files, end=-1 for EOF\n", |
381 | 387 | " nums:bool=False # Whether to show line numbers\n", |
382 | 388 | "):\n", |
383 | 389 | " 'View directory or file contents with optional line range and numbers'\n", |
384 | 390 | " try:\n", |
385 | | - " p = Path(path).expanduser().resolve()\n", |
386 | | - " if not p.exists(): return f'Error: File not found: {p}'\n", |
| 391 | + " p = valid_path(path)\n", |
387 | 392 | " header = None\n", |
388 | 393 | " if p.is_dir():\n", |
389 | 394 | " files = [str(f) for f in p.glob('**/*') \n", |
|
456 | 461 | "name": "stdout", |
457 | 462 | "output_type": "stream", |
458 | 463 | "text": [ |
459 | | - "Directory contents of /Users/jhoward/aai-ws/fastcore/nbs:\n", |
460 | | - "/Users/jhoward/aai-ws/fastcore/nbs/llms.txt\n", |
461 | | - "/Users/jhoward/aai-ws/fastcore/nbs/000_tour.ipynb\n", |
462 | | - "/Users/jhoward/aai-ws/fastcore/nbs/parallel_test.py\n", |
463 | | - "/Users/jhoward/aai-ws/fastcore/nbs/_quarto.yml\n", |
464 | | - "/Users/jhoward/aai-ws/fastcore/nbs/08_style.ipynb\n" |
| 464 | + "Directory contents of /path:\n", |
| 465 | + "/path/000_tour.ipynb\n", |
| 466 | + "/path/00_test.ipynb\n", |
| 467 | + "/path/01_basics.ipynb\n", |
| 468 | + "/path/02_foundation.ipynb\n", |
| 469 | + "/path/03_xtras.ipynb\n" |
465 | 470 | ] |
466 | 471 | } |
467 | 472 | ], |
468 | 473 | "source": [ |
469 | | - "print(view('.', (1,5)))" |
| 474 | + "print(view('.', (1,5)).replace(os.getcwd(), '/path'))" |
470 | 475 | ] |
471 | 476 | }, |
472 | 477 | { |
|
484 | 489 | ") -> str:\n", |
485 | 490 | " 'Creates a new file with the given content at the specified path'\n", |
486 | 491 | " try:\n", |
487 | | - " p = Path(path)\n", |
| 492 | + " p = valid_path(path, must_exist=False)\n", |
488 | 493 | " if p.exists():\n", |
489 | 494 | " if not overwrite: return f'Error: File already exists: {p}'\n", |
490 | 495 | " p.parent.mkdir(parents=True, exist_ok=True)\n", |
|
503 | 508 | "name": "stdout", |
504 | 509 | "output_type": "stream", |
505 | 510 | "text": [ |
506 | | - "Created file test.txt.\n", |
| 511 | + "Created file /path/test.txt.\n", |
507 | 512 | "Contents:\n", |
508 | 513 | " 1 │ Hello, world!\n" |
509 | 514 | ] |
510 | 515 | } |
511 | 516 | ], |
512 | 517 | "source": [ |
513 | | - "print(create('test.txt', 'Hello, world!'))\n", |
| 518 | + "print(create('test.txt', 'Hello, world!').replace(os.getcwd(), '/path'))\n", |
514 | 519 | "f = Path('test.txt')\n", |
515 | 520 | "test_eq(f.exists(), True)\n", |
516 | 521 | "print('Contents:\\n', view(f, nums=True))" |
|
531 | 536 | ") -> str:\n", |
532 | 537 | " 'Insert new_str at specified line number'\n", |
533 | 538 | " try:\n", |
534 | | - " p = Path(path)\n", |
535 | | - " if not p.exists(): return f'Error: File not found: {p}'\n", |
| 539 | + " p = valid_path(path)\n", |
536 | 540 | " content = p.read_text().splitlines()\n", |
537 | 541 | " if not (0 <= insert_line <= len(content)): return f'Error: Invalid line number {insert_line}'\n", |
538 | 542 | " content.insert(insert_line, new_str)\n", |
|
577 | 581 | ") -> str:\n", |
578 | 582 | " 'Replace first occurrence of old_str with new_str in file'\n", |
579 | 583 | " try:\n", |
580 | | - " p = Path(path)\n", |
581 | | - " if not p.exists(): return f'Error: File not found: {p}'\n", |
| 584 | + " p = valid_path(path)\n", |
582 | 585 | " content = p.read_text()\n", |
583 | 586 | " count = content.count(old_str)\n", |
584 | 587 | " if count == 0: return 'Error: Text not found in file'\n", |
|
663 | 666 | " new_content:str, # New content to replace the specified lines\n", |
664 | 667 | "):\n", |
665 | 668 | " \"Replace lines in file using start and end line-numbers (index starting at 1)\"\n", |
666 | | - " if not (p := Path(path)).exists(): return f\"Error: File not found: {p}\"\n", |
667 | | - " content = p.readlines()\n", |
668 | | - " if not new_content.endswith('\\n'): new_content+='\\n'\n", |
669 | | - " content[start_line-1:end_line] = [new_content]\n", |
670 | | - " p.write_text(''.join(content))\n", |
671 | | - " return f\"Replaced lines {start_line} to {end_line}.\"" |
| 669 | + " try:\n", |
| 670 | + " p = valid_path(path)\n", |
| 671 | + " content = p.readlines()\n", |
| 672 | + " if not new_content.endswith('\\n'): new_content+='\\n'\n", |
| 673 | + " content[start_line-1:end_line] = [new_content]\n", |
| 674 | + " p.write_text(''.join(content))\n", |
| 675 | + " return f\"Replaced lines {start_line} to {end_line}.\"\n", |
| 676 | + " except Exception as e: return f'Error replacing lines: {str(e)}'" |
672 | 677 | ] |
673 | 678 | }, |
674 | 679 | { |
|
691 | 696 | "print(view('test.txt', nums=True))" |
692 | 697 | ] |
693 | 698 | }, |
| 699 | + { |
| 700 | + "cell_type": "code", |
| 701 | + "execution_count": null, |
| 702 | + "id": "0a6b79f4", |
| 703 | + "metadata": {}, |
| 704 | + "outputs": [ |
| 705 | + { |
| 706 | + "data": { |
| 707 | + "text/plain": [ |
| 708 | + "'Error replacing lines: File not found: /path/missing.txt'" |
| 709 | + ] |
| 710 | + }, |
| 711 | + "execution_count": null, |
| 712 | + "metadata": {}, |
| 713 | + "output_type": "execute_result" |
| 714 | + } |
| 715 | + ], |
| 716 | + "source": [ |
| 717 | + "replace_lines('missing.txt', 1, 2, 'Replaced first two lines').replace(os.getcwd(), '/path')" |
| 718 | + ] |
| 719 | + }, |
694 | 720 | { |
695 | 721 | "cell_type": "code", |
696 | 722 | "execution_count": null, |
|
706 | 732 | " dest_line: int, # Destination line number (1-based, where lines will be inserted before)\n", |
707 | 733 | ") -> str:\n", |
708 | 734 | " \"Move lines from start_line:end_line to before dest_line\"\n", |
709 | | - " if not (p := Path(path)).exists(): return f\"Error: File not found: {p}\"\n", |
710 | | - " lines = p.read_text().splitlines()\n", |
711 | | - " if not (1 <= start_line <= end_line <= len(lines)): return f\"Error: Invalid range {start_line}-{end_line}\"\n", |
712 | | - " if not (1 <= dest_line <= len(lines) + 1): return f\"Error: Invalid destination {dest_line}\"\n", |
713 | | - " if start_line <= dest_line <= end_line + 1: return \"Error: Destination within source range\"\n", |
714 | | - " \n", |
715 | | - " chunk = lines[start_line-1:end_line]\n", |
716 | | - " del lines[start_line-1:end_line]\n", |
717 | | - " # Adjust dest if it was after the removed chunk\n", |
718 | | - " if dest_line > end_line: dest_line -= len(chunk)\n", |
719 | | - " lines[dest_line-1:dest_line-1] = chunk\n", |
720 | | - " p.write_text('\\n'.join(lines) + '\\n')\n", |
721 | | - " return f\"Moved lines {start_line}-{end_line} to line {dest_line}\"" |
| 735 | + " try:\n", |
| 736 | + " p = valid_path(path)\n", |
| 737 | + " lines = p.read_text().splitlines()\n", |
| 738 | + " assert 1 <= start_line <= end_line <= len(lines), f\"Invalid range {start_line}-{end_line}\"\n", |
| 739 | + " assert 1 <= dest_line <= len(lines) + 1, f\"Invalid destination {dest_line}\"\n", |
| 740 | + " assert not(start_line <= dest_line <= end_line + 1), \"Destination within source range\"\n", |
| 741 | + " \n", |
| 742 | + " chunk = lines[start_line-1:end_line]\n", |
| 743 | + " del lines[start_line-1:end_line]\n", |
| 744 | + " # Adjust dest if it was after the removed chunk\n", |
| 745 | + " if dest_line > end_line: dest_line -= len(chunk)\n", |
| 746 | + " lines[dest_line-1:dest_line-1] = chunk\n", |
| 747 | + " p.write_text('\\n'.join(lines) + '\\n')\n", |
| 748 | + " return f\"Moved lines {start_line}-{end_line} to line {dest_line}\"\n", |
| 749 | + " except Exception as e: return f'Error: {str(e)}'" |
722 | 750 | ] |
723 | 751 | }, |
724 | 752 | { |
|
838 | 866 | "text": [ |
839 | 867 | "Error: Destination within source range\n", |
840 | 868 | "Error: Invalid range 10-12\n", |
841 | | - "Error: Invalid destination 99\n" |
| 869 | + "Error: Invalid destination 99\n", |
| 870 | + "Error: File not found: /path/mising.txt\n" |
842 | 871 | ] |
843 | 872 | } |
844 | 873 | ], |
845 | 874 | "source": [ |
846 | 875 | "print(move_lines('move_test.txt', 2, 3, 3)) # dest within source range\n", |
847 | 876 | "print(move_lines('move_test.txt', 10, 12, 1)) # invalid range\n", |
848 | | - "print(move_lines('move_test.txt', 1, 2, 99)) # invalid destination" |
| 877 | + "print(move_lines('move_test.txt', 1, 2, 99)) # invalid destination\n", |
| 878 | + "print(move_lines('mising.txt', 1, 2, 99).replace(os.getcwd(), '/path')) # missing file" |
849 | 879 | ] |
850 | 880 | }, |
851 | 881 | { |
|
0 commit comments