1# -*- coding: utf-8 -*- 2 3""" 4Filesystem Operations 5""" 6 7import os 8import pathlib 9import shutil 10import tempfile 11import time 12 13import os.path as osp 14 15from mathics.version import __version__ # noqa used in loading to check consistency. 16 17from mathics.core.expression import ( 18 Expression, 19 String, 20 Symbol, 21 SymbolFailed, 22 SymbolFalse, 23 SymbolNull, 24 SymbolTrue, 25 SymbolList, 26 from_python, 27 valid_context_name, 28) 29 30import mathics.core.streams 31from mathics.core.streams import ( 32 HOME_DIR, 33 PATH_VAR, 34 ROOT_DIR, 35 create_temporary_file, 36 path_search, 37 urlsave_tmp, 38) 39 40from mathics.builtin.base import Builtin, Predefined 41from mathics.builtin.files_io.files import ( 42 DIRECTORY_STACK, 43 INITIAL_DIR, # noqa is used via global 44 mathics_open 45 ) 46from mathics.builtin.numeric import Hash 47from mathics.builtin.strings import to_regex 48from mathics.builtin.base import MessageException 49import re 50 51SYS_ROOT_DIR = "/" if os.name == "posix" else "\\" 52TMP_DIR = tempfile.gettempdir() 53 54 55class AbsoluteFileName(Builtin): 56 """ 57 <dl> 58 <dt>'AbsoluteFileName["$name$"]' 59 <dd>returns the absolute version of the given filename. 60 </dl> 61 62 >> AbsoluteFileName["ExampleData/sunflowers.jpg"] 63 = ... 64 65 #> AbsoluteFileName["Some/NonExistant/Path.ext"] 66 : File not found during AbsoluteFileName[Some/NonExistant/Path.ext]. 67 = $Failed 68 """ 69 70 attributes = "Protected" 71 72 messages = { 73 "fstr": ("File specification x is not a string of one or more characters."), 74 "nffil": "File not found during `1`.", 75 } 76 77 def apply(self, name, evaluation): 78 "AbsoluteFileName[name_]" 79 80 py_name = name.to_python() 81 82 if not (isinstance(py_name, str) and py_name[0] == py_name[-1] == '"'): 83 evaluation.message("AbsoluteFileName", "fstr", name) 84 return 85 py_name = py_name[1:-1] 86 87 result = path_search(py_name) 88 89 if result is None: 90 evaluation.message( 91 "AbsoluteFileName", "nffil", Expression("AbsoluteFileName", name) 92 ) 93 return SymbolFailed 94 95 return String(osp.abspath(result)) 96 97 98class BaseDirectory(Predefined): 99 """ 100 <dl> 101 <dt>'$UserBaseDirectory' 102 <dd>returns the folder where user configurations are stored. 103 </dl> 104 105 >> $RootDirectory 106 = ... 107 """ 108 109 name = "$BaseDirectory" 110 111 attributes = "Protected" 112 113 def evaluate(self, evaluation): 114 global ROOT_DIR 115 return String(ROOT_DIR) 116 117 118class CopyDirectory(Builtin): 119 """ 120 <dl> 121 <dt>'CopyDirectory["$dir1$", "$dir2$"]' 122 <dd>copies directory $dir1$ to $dir2$. 123 </dl> 124 """ 125 126 attributes = "Protected" 127 128 messages = { 129 "argr": "called with `1` argument; 2 arguments are expected.", 130 "fstr": ( 131 "File specification `1` is not a string of " "one or more characters." 132 ), 133 "filex": "Cannot overwrite existing file `1`.", 134 "nodir": "Directory `1` not found.", 135 } 136 137 def apply(self, dirs, evaluation): 138 "CopyDirectory[dirs__]" 139 140 seq = dirs.get_sequence() 141 if len(seq) != 2: 142 evaluation.message("CopyDirectory", "argr", len(seq)) 143 return 144 (dir1, dir2) = (s.to_python() for s in seq) 145 146 if not (isinstance(dir1, str) and dir1[0] == dir1[-1] == '"'): 147 evaluation.message("CopyDirectory", "fstr", seq[0]) 148 return 149 dir1 = dir1[1:-1] 150 151 if not (isinstance(dir2, str) and dir2[0] == dir2[-1] == '"'): 152 evaluation.message("CopyDirectory", "fstr", seq[1]) 153 return 154 dir2 = dir2[1:-1] 155 156 if not osp.isdir(dir1): 157 evaluation.message("CopyDirectory", "nodir", seq[0]) 158 return SymbolFailed 159 if osp.isdir(dir2): 160 evaluation.message("CopyDirectory", "filex", seq[1]) 161 return SymbolFailed 162 163 shutil.copytree(dir1, dir2) 164 165 return String(osp.abspath(dir2)) 166 167 168class CopyFile(Builtin): 169 """ 170 <dl> 171 <dt>'CopyFile["$file1$", "$file2$"]' 172 <dd>copies $file1$ to $file2$. 173 </dl> 174 175 X> CopyFile["ExampleData/sunflowers.jpg", "MathicsSunflowers.jpg"] 176 = MathicsSunflowers.jpg 177 X> DeleteFile["MathicsSunflowers.jpg"] 178 """ 179 180 messages = { 181 "filex": "Cannot overwrite existing file `1`.", 182 "fstr": ( 183 "File specification `1` is not a string of " "one or more characters." 184 ), 185 "nffil": "File not found during `1`.", 186 } 187 188 attributes = "Protected" 189 190 def apply(self, source, dest, evaluation): 191 "CopyFile[source_, dest_]" 192 193 py_source = source.to_python() 194 py_dest = dest.to_python() 195 196 # Check filenames 197 if not (isinstance(py_source, str) and py_source[0] == py_source[-1] == '"'): 198 evaluation.message("CopyFile", "fstr", source) 199 return 200 if not (isinstance(py_dest, str) and py_dest[0] == py_dest[-1] == '"'): 201 evaluation.message("CopyFile", "fstr", dest) 202 return 203 204 py_source = py_source[1:-1] 205 py_dest = py_dest[1:-1] 206 207 py_source = path_search(py_source) 208 209 if py_source is None: 210 evaluation.message("CopyFile", "filex", source) 211 return SymbolFailed 212 213 if osp.exists(py_dest): 214 evaluation.message("CopyFile", "filex", dest) 215 return SymbolFailed 216 217 try: 218 shutil.copy(py_source, py_dest) 219 except IOError: 220 evaluation.message( 221 "CopyFile", "nffil", Expression("CopyFile", source, dest) 222 ) 223 return SymbolFailed 224 225 return dest 226 227 228class CreateDirectory(Builtin): 229 """ 230 <dl> 231 <dt>'CreateDirectory["$dir$"]' 232 <dd>creates a directory called $dir$. 233 <dt>'CreateDirectory[]' 234 <dd>creates a temporary directory. 235 </dl> 236 237 >> dir = CreateDirectory[] 238 = ... 239 #> DirectoryQ[dir] 240 = True 241 #> DeleteDirectory[dir] 242 """ 243 244 attributes = ("Listable", "Protected") 245 246 options = { 247 "CreateIntermediateDirectories": "True", 248 } 249 250 messages = { 251 "fstr": ( 252 "File specification `1` is not a string of " "one or more characters." 253 ), 254 "nffil": "File not found during `1`.", 255 "filex": "`1` already exists.", 256 } 257 258 def apply(self, dirname, evaluation, options): 259 "CreateDirectory[dirname_, OptionsPattern[CreateDirectory]]" 260 261 expr = Expression("CreateDirectory", dirname) 262 py_dirname = dirname.to_python() 263 264 if not (isinstance(py_dirname, str) and py_dirname[0] == py_dirname[-1] == '"'): 265 evaluation.message("CreateDirectory", "fstr", dirname) 266 return 267 268 py_dirname = py_dirname[1:-1] 269 270 if osp.isdir(py_dirname): 271 evaluation.message("CreateDirectory", "filex", osp.abspath(py_dirname)) 272 return 273 274 os.mkdir(py_dirname) 275 276 if not osp.isdir(py_dirname): 277 evaluation.message("CreateDirectory", "nffil", expr) 278 return 279 280 return String(osp.abspath(py_dirname)) 281 282 def apply_empty(self, evaluation, options): 283 "CreateDirectory[OptionsPattern[CreateDirectory]]" 284 dirname = tempfile.mkdtemp(prefix="m", dir=TMP_DIR) 285 return String(dirname) 286 287 288class CreateFile(Builtin): 289 """ 290 <dl> 291 <dt>'CreateFile["filename"]' 292 <dd>Creates a file named "filename" temporary file, but do not open it. 293 <dt>'CreateFile[]' 294 <dd>Creates a temporary file, but do not open it. 295 </dl> 296 """ 297 298 rules = { 299 "CreateFile[]": "CreateTemporary[]", 300 } 301 options = { 302 "CreateIntermediateDirectories": "True", 303 "OverwriteTarget": "True", 304 } 305 306 attributes = ("Listable", "Protected") 307 308 def apply(self, filename, evaluation, **options): 309 "CreateFile[filename_String, OptionsPattern[CreateFile]]" 310 try: 311 # TODO: Implement options 312 if not osp.isfile(filename.value): 313 f = open(filename.value, "w") 314 res = f.name 315 f.close() 316 return String(res) 317 else: 318 return filename 319 except: 320 return SymbolFailed 321 322 323class CreateTemporary(Builtin): 324 """ 325 <dl> 326 <dt>'CreateTemporary[]' 327 <dd>Creates a temporary file, but do not open it. 328 </dl> 329 """ 330 331 def apply_0(self, evaluation): 332 "CreateTemporary[]" 333 try: 334 res = create_temporary_file() 335 except: 336 return SymbolFailed 337 return String(res) 338 339 340class DeleteDirectory(Builtin): 341 """ 342 <dl> 343 <dt>'DeleteDirectory["$dir$"]' 344 <dd>deletes a directory called $dir$. 345 </dl> 346 347 >> dir = CreateDirectory[] 348 = ... 349 >> DeleteDirectory[dir] 350 >> DirectoryQ[dir] 351 = False 352 #> Quiet[DeleteDirectory[dir]] 353 = $Failed 354 """ 355 356 attributes = "Protected" 357 358 options = { 359 "DeleteContents": "False", 360 } 361 362 messages = { 363 "strs": ( 364 "String or non-empty list of strings expected at " "position 1 in `1`." 365 ), 366 "nodir": "Directory `1` not found.", 367 "dirne": "Directory `1` not empty.", 368 "optx": "Unknown option `1` in `2`", 369 "idcts": "DeleteContents expects either True or False.", # MMA Bug 370 } 371 372 def apply(self, dirname, evaluation, options): 373 "DeleteDirectory[dirname_, OptionsPattern[DeleteDirectory]]" 374 375 expr = Expression("DeleteDirectory", dirname) 376 py_dirname = dirname.to_python() 377 378 delete_contents = options["System`DeleteContents"].to_python() 379 if delete_contents not in [True, False]: 380 evaluation.message("DeleteDirectory", "idcts") 381 return 382 383 if not (isinstance(py_dirname, str) and py_dirname[0] == py_dirname[-1] == '"'): 384 evaluation.message("DeleteDirectory", "strs", expr) 385 return 386 387 py_dirname = py_dirname[1:-1] 388 389 if not osp.isdir(py_dirname): 390 evaluation.message("DeleteDirectory", "nodir", dirname) 391 return SymbolFailed 392 393 if delete_contents: 394 shutil.rmtree(py_dirname) 395 else: 396 if os.listdir(py_dirname) != []: 397 evaluation.message("DeleteDirectory", "dirne", dirname) 398 return SymbolFailed 399 os.rmdir(py_dirname) 400 401 return SymbolNull 402 403 404class DeleteFile(Builtin): 405 """ 406 <dl> 407 <dt>'Delete["$file$"]' 408 <dd>deletes $file$. 409 <dt>'Delete[{"$file1$", "$file2$", ...}]' 410 <dd>deletes a list of files. 411 </dl> 412 413 >> CopyFile["ExampleData/sunflowers.jpg", "MathicsSunflowers.jpg"]; 414 >> DeleteFile["MathicsSunflowers.jpg"] 415 416 >> CopyFile["ExampleData/sunflowers.jpg", "MathicsSunflowers1.jpg"]; 417 >> CopyFile["ExampleData/sunflowers.jpg", "MathicsSunflowers2.jpg"]; 418 >> DeleteFile[{"MathicsSunflowers1.jpg", "MathicsSunflowers2.jpg"}] 419 """ 420 421 messages = { 422 "filex": "Cannot overwrite existing file `1`.", 423 "strs": ( 424 "String or non-empty list of strings expected at " "position `1` in `2`." 425 ), 426 "nffil": "File not found during `1`.", 427 } 428 429 attributes = "Protected" 430 431 def apply(self, filename, evaluation): 432 "DeleteFile[filename_]" 433 434 py_path = filename.to_python() 435 if not isinstance(py_path, list): 436 py_path = [py_path] 437 438 py_paths = [] 439 for path in py_path: 440 # Check filenames 441 if not (isinstance(path, str) and path[0] == path[-1] == '"'): 442 evaluation.message( 443 "DeleteFile", "strs", filename, Expression("DeleteFile", filename) 444 ) 445 return 446 447 path = path[1:-1] 448 path = path_search(path) 449 450 if path is None: 451 evaluation.message( 452 "DeleteFile", "nffil", Expression("DeleteFile", filename) 453 ) 454 return SymbolFailed 455 py_paths.append(path) 456 457 for path in py_paths: 458 try: 459 os.remove(path) 460 except OSError: 461 return SymbolFailed 462 463 return SymbolNull 464 465 466class Directory(Builtin): 467 """ 468 <dl> 469 <dt>'Directory[]' 470 <dd>returns the current working directory. 471 </dl> 472 473 >> Directory[] 474 = ... 475 """ 476 477 attributes = "Protected" 478 479 def apply(self, evaluation): 480 "Directory[]" 481 result = os.getcwd() 482 return String(result) 483 484 485class DirectoryName(Builtin): 486 """ 487 <dl> 488 <dt>'DirectoryName["$name$"]' 489 <dd>extracts the directory name from a filename. 490 </dl> 491 492 >> DirectoryName["a/b/c"] 493 = a/b 494 495 >> DirectoryName["a/b/c", 2] 496 = a 497 498 #> DirectoryName["a/b/c", 3] // InputForm 499 = "" 500 #> DirectoryName[""] // InputForm 501 = "" 502 503 #> DirectoryName["a/b/c", x] 504 : Positive machine-sized integer expected at position 2 in DirectoryName[a/b/c, x]. 505 = DirectoryName[a/b/c, x] 506 507 #> DirectoryName["a/b/c", -1] 508 : Positive machine-sized integer expected at position 2 in DirectoryName[a/b/c, -1]. 509 = DirectoryName[a/b/c, -1] 510 511 #> DirectoryName[x] 512 : String expected at position 1 in DirectoryName[x]. 513 = DirectoryName[x] 514 """ 515 516 attributes = "Protected" 517 518 options = { 519 "OperatingSystem": "$OperatingSystem", 520 } 521 522 messages = { 523 "string": "String expected at position 1 in `1`.", 524 "intpm": ("Positive machine-sized integer expected at " "position 2 in `1`."), 525 } 526 527 def apply_with_n(self, name, n, evaluation, options): 528 "DirectoryName[name_, n_, OptionsPattern[DirectoryName]]" 529 530 if n is None: 531 expr = Expression("DirectoryName", name) 532 py_n = 1 533 else: 534 expr = Expression("DirectoryName", name, n) 535 py_n = n.to_python() 536 537 if not (isinstance(py_n, int) and py_n > 0): 538 evaluation.message("DirectoryName", "intpm", expr) 539 return 540 541 py_name = name.to_python() 542 if not (isinstance(py_name, str) and py_name[0] == py_name[-1] == '"'): 543 evaluation.message("DirectoryName", "string", expr) 544 return 545 py_name = py_name[1:-1] 546 547 result = py_name 548 for i in range(py_n): 549 (result, tmp) = osp.split(result) 550 551 return String(result) 552 553 def apply(self, name, evaluation, options): 554 "DirectoryName[name_, OptionsPattern[DirectoryName]]" 555 return self.apply_with_n(name, None, evaluation, options) 556 557 558class DirectoryStack(Builtin): 559 """ 560 <dl> 561 <dt>'DirectoryStack[]' 562 <dd>returns the directory stack. 563 </dl> 564 565 >> DirectoryStack[] 566 = ... 567 """ 568 569 attributes = "Protected" 570 571 def apply(self, evaluation): 572 "DirectoryStack[]" 573 global DIRECTORY_STACK 574 return from_python(DIRECTORY_STACK) 575 576 577class DirectoryQ(Builtin): 578 """ 579 <dl> 580 <dt>'DirectoryQ["$name$"]' 581 <dd>returns 'True' if the directory called $name$ exists and 'False' otherwise. 582 </dl> 583 584 >> DirectoryQ["ExampleData/"] 585 = True 586 >> DirectoryQ["ExampleData/MythicalSubdir/"] 587 = False 588 589 #> DirectoryQ["ExampleData"] 590 = True 591 592 #> DirectoryQ["ExampleData/MythicalSubdir/NestedDir/"] 593 = False 594 """ 595 596 messages = { 597 "fstr": ( 598 "File specification `1` is not a string of " "one or more characters." 599 ), 600 } 601 602 attributes = "Protected" 603 604 def apply(self, pathname, evaluation): 605 "DirectoryQ[pathname_]" 606 path = pathname.to_python() 607 608 if not (isinstance(path, str) and path[0] == path[-1] == '"'): 609 evaluation.message("DirectoryQ", "fstr", pathname) 610 return 611 path = path[1:-1] 612 613 path = path_search(path) 614 615 if path is not None and osp.isdir(path): 616 return SymbolTrue 617 return SymbolFalse 618 619 620class ExpandFileName(Builtin): 621 """ 622 <dl> 623 <dt>'ExpandFileName["$name$"]' 624 <dd>expands $name$ to an absolute filename for your system. 625 </dl> 626 627 >> ExpandFileName["ExampleData/sunflowers.jpg"] 628 = ... 629 """ 630 631 attributes = "Protected" 632 633 messages = { 634 "string": "String expected at position 1 in `1`.", 635 } 636 637 def apply(self, name, evaluation): 638 "ExpandFileName[name_]" 639 640 py_name = name.to_python() 641 642 if not (isinstance(py_name, str) and py_name[0] == py_name[-1] == '"'): 643 evaluation.message( 644 "ExpandFileName", "string", Expression("ExpandFileName", name) 645 ) 646 return 647 py_name = py_name[1:-1] 648 649 return String(osp.abspath(py_name)) 650 651 652class File(Builtin): 653 attributes = "Protected" 654 655 656class FileBaseName(Builtin): 657 """ 658 <dl> 659 <dt>'FileBaseName["$file$"]' 660 <dd>gives the base name for the specified file name. 661 </dl> 662 663 >> FileBaseName["file.txt"] 664 = file 665 666 >> FileBaseName["file.tar.gz"] 667 = file.tar 668 669 #> FileBaseName["file."] 670 = file 671 672 #> FileBaseName["file"] 673 = file 674 """ 675 676 attributes = "Protected" 677 678 options = { 679 "OperatingSystem": "$OperatingSystem", 680 } 681 682 def apply(self, filename, evaluation, options): 683 "FileBaseName[filename_String, OptionsPattern[FileBaseName]]" 684 path = filename.to_python()[1:-1] 685 686 filename_base, filename_ext = osp.splitext(path) 687 return from_python(filename_base) 688 689 690class FileByteCount(Builtin): 691 """ 692 <dl> 693 <dt>'FileByteCount[$file$]' 694 <dd>returns the number of bytes in $file$. 695 </dl> 696 697 >> FileByteCount["ExampleData/sunflowers.jpg"] 698 = 142286 699 """ 700 701 messages = { 702 "fstr": "File specification `1` is not a string of one or more characters.", 703 } 704 705 def apply(self, filename, evaluation): 706 "FileByteCount[filename_]" 707 py_filename = filename.to_python() 708 if not ( 709 isinstance(py_filename, str) and py_filename[0] == py_filename[-1] == '"' 710 ): 711 evaluation.message("FileByteCount", "fstr", filename) 712 return 713 py_filename = py_filename[1:-1] 714 715 try: 716 with mathics_open(py_filename, "rb") as f: 717 count = 0 718 tmp = f.read(1) 719 while tmp != b"": 720 count += 1 721 tmp = f.read(1) 722 723 except IOError: 724 evaluation.message("General", "noopen", filename) 725 return 726 except MessageException as e: 727 e.message(evaluation) 728 return 729 730 return from_python(count) 731 732 733class FileDate(Builtin): 734 """ 735 <dl> 736 <dt>'FileDate[$file$, $types$]' 737 <dd>returns the time and date at which the file was last modified. 738 </dl> 739 740 >> FileDate["ExampleData/sunflowers.jpg"] 741 = ... 742 743 >> FileDate["ExampleData/sunflowers.jpg", "Access"] 744 = ... 745 746 >> FileDate["ExampleData/sunflowers.jpg", "Creation"] 747 = ... 748 749 >> FileDate["ExampleData/sunflowers.jpg", "Change"] 750 = ... 751 752 >> FileDate["ExampleData/sunflowers.jpg", "Modification"] 753 = ... 754 755 >> FileDate["ExampleData/sunflowers.jpg", "Rules"] 756 = ... 757 758 #> FileDate["MathicsNonExistantExample"] 759 : File not found during FileDate[MathicsNonExistantExample]. 760 = FileDate[MathicsNonExistantExample] 761 #> FileDate["MathicsNonExistantExample", "Modification"] 762 : File not found during FileDate[MathicsNonExistantExample, Modification]. 763 = FileDate[MathicsNonExistantExample, Modification] 764 765 #> FileDate["ExampleData/sunflowers.jpg", "Fail"] 766 : Date type Fail should be "Access", "Modification", "Creation" (Windows only), "Change" (Macintosh and Unix only), or "Rules". 767 = FileDate[ExampleData/sunflowers.jpg, Fail] 768 """ 769 770 messages = { 771 "nffil": "File not found during `1`.", 772 "datetype": ( 773 'Date type Fail should be "Access", "Modification", ' 774 '"Creation" (Windows only), ' 775 '"Change" (Macintosh and Unix only), or "Rules".' 776 ), 777 } 778 779 rules = { 780 'FileDate[filepath_String, "Rules"]': """{"Access" -> FileDate[filepath, "Access"], 781 "Creation" -> FileDate[filepath, "Creation"], 782 "Change" -> FileDate[filepath, "Change"], 783 "Modification" -> FileDate[filepath, "Modification"]}""", 784 } 785 786 attributes = "Protected" 787 788 def apply(self, path, timetype, evaluation): 789 "FileDate[path_, timetype_]" 790 py_path = path_search(path.to_python()[1:-1]) 791 792 if py_path is None: 793 if timetype is None: 794 evaluation.message("FileDate", "nffil", Expression("FileDate", path)) 795 else: 796 evaluation.message( 797 "FileDate", "nffil", Expression("FileDate", path, timetype) 798 ) 799 return 800 801 if timetype is None: 802 time_type = "Modification" 803 else: 804 time_type = timetype.to_python()[1:-1] 805 806 if time_type == "Access": 807 result = osp.getatime(py_path) 808 elif time_type == "Creation": 809 if os.name == "posix": 810 return Expression("Missing", "NotApplicable") 811 result = osp.getctime(py_path) 812 elif time_type == "Change": 813 if os.name != "posix": 814 return Expression("Missing", "NotApplicable") 815 result = osp.getctime(py_path) 816 elif time_type == "Modification": 817 result = osp.getmtime(py_path) 818 else: 819 evaluation.message("FileDate", "datetype") 820 return 821 822 # Offset for system epoch 823 epochtime = Expression( 824 "AbsoluteTime", time.strftime("%Y-%m-%d %H:%M", time.gmtime(0)) 825 ).to_python(n_evaluation=evaluation) 826 result += epochtime 827 828 return Expression("DateList", from_python(result)) 829 830 def apply_default(self, path, evaluation): 831 "FileDate[path_]" 832 return self.apply(path, None, evaluation) 833 834 835class FileExistsQ(Builtin): 836 """ 837 <dl> 838 <dt>'FileExistsQ["$file$"]' 839 <dd>returns 'True' if $file$ exists and 'False' otherwise. 840 </dl> 841 842 >> FileExistsQ["ExampleData/sunflowers.jpg"] 843 = True 844 >> FileExistsQ["ExampleData/sunflowers.png"] 845 = False 846 """ 847 848 messages = { 849 "fstr": ( 850 "File specification `1` is not a string of " "one or more characters." 851 ), 852 } 853 854 attributes = "Protected" 855 856 def apply(self, filename, evaluation): 857 "FileExistsQ[filename_]" 858 path = filename.to_python() 859 if not (isinstance(path, str) and path[0] == path[-1] == '"'): 860 evaluation.message("FileExistsQ", "fstr", filename) 861 return 862 path = path[1:-1] 863 864 path = path_search(path) 865 866 if path is None: 867 return SymbolFalse 868 return SymbolTrue 869 870 871class FileExtension(Builtin): 872 """ 873 <dl> 874 <dt>'FileExtension["$file$"]' 875 <dd>gives the extension for the specified file name. 876 </dl> 877 878 >> FileExtension["file.txt"] 879 = txt 880 881 >> FileExtension["file.tar.gz"] 882 = gz 883 884 #> FileExtension["file."] 885 = #<--# 886 #> FileExtension["file"] 887 = #<--# 888 """ 889 890 attributes = "Protected" 891 892 options = { 893 "OperatingSystem": "$OperatingSystem", 894 } 895 896 def apply(self, filename, evaluation, options): 897 "FileExtension[filename_String, OptionsPattern[FileExtension]]" 898 path = filename.to_python()[1:-1] 899 filename_base, filename_ext = osp.splitext(path) 900 filename_ext = filename_ext.lstrip(".") 901 return from_python(filename_ext) 902 903 904class FileHash(Builtin): 905 """ 906 <dl> 907 <dt>'FileHash[$file$]' 908 <dd>returns an integer hash for the given $file$. 909 <dt>'FileHash[$file$, $type$]' 910 <dd>returns an integer hash of the specified $type$ for the given $file$.</dd> 911 <dd>The types supported are "MD5", "Adler32", "CRC32", "SHA", "SHA224", "SHA256", "SHA384", and "SHA512".</dd> 912 <dt>'FileHash[$file$, $type$, $format$]' 913 <dd>gives a hash code in the specified format.</dd> 914 </dl> 915 916 >> FileHash["ExampleData/sunflowers.jpg"] 917 = 109937059621979839952736809235486742106 918 919 >> FileHash["ExampleData/sunflowers.jpg", "MD5"] 920 = 109937059621979839952736809235486742106 921 922 >> FileHash["ExampleData/sunflowers.jpg", "Adler32"] 923 = 1607049478 924 925 >> FileHash["ExampleData/sunflowers.jpg", "SHA256"] 926 = 111619807552579450300684600241129773909359865098672286468229443390003894913065 927 928 #> FileHash["ExampleData/sunflowers.jpg", "CRC32"] 929 = 933095683 930 #> FileHash["ExampleData/sunflowers.jpg", "SHA"] 931 = 851696818771101405642332645949480848295550938123 932 #> FileHash["ExampleData/sunflowers.jpg", "SHA224"] 933 = 8723805623766373862936267623913366865806344065103917676078120867011 934 #> FileHash["ExampleData/sunflowers.jpg", "SHA384"] 935 = 28288410602533803613059815846847184383722061845493818218404754864571944356226472174056863474016709057507799332611860 936 #> FileHash["ExampleData/sunflowers.jpg", "SHA512"] 937 = 10111462070211820348006107532340854103555369343736736045463376555356986226454343186097958657445421102793096729074874292511750542388324853755795387877480102 938 939 #> FileHash["ExampleData/sunflowers.jpg", xyzsymbol] 940 = FileHash[ExampleData/sunflowers.jpg, xyzsymbol] 941 #> FileHash["ExampleData/sunflowers.jpg", "xyzstr"] 942 = FileHash[ExampleData/sunflowers.jpg, xyzstr, Integer] 943 #> FileHash[xyzsymbol] 944 = FileHash[xyzsymbol] 945 """ 946 947 rules = { 948 "FileHash[filename_String]": 'FileHash[filename, "MD5", "Integer"]', 949 "FileHash[filename_String, hashtype_String]": 'FileHash[filename, hashtype, "Integer"]', 950 } 951 952 attributes = ("Protected", "ReadProtected") 953 954 def apply(self, filename, hashtype, format, evaluation): 955 "FileHash[filename_String, hashtype_String, format_String]" 956 py_filename = filename.get_string_value() 957 958 try: 959 with mathics_open(py_filename, "rb") as f: 960 dump = f.read() 961 except IOError: 962 evaluation.message("General", "noopen", filename) 963 return 964 except MessageException as e: 965 e.message(evaluation) 966 return 967 968 return Hash.compute( 969 lambda update: update(dump), 970 hashtype.get_string_value(), 971 format.get_string_value(), 972 ) 973 974 975class FileInformation(Builtin): 976 """ 977 <dl> 978 <dt>'FileInformation["$file$"]' 979 <dd>returns information about $file$. 980 </dl> 981 982 This function is totally undocumented in MMA! 983 984 >> FileInformation["ExampleData/sunflowers.jpg"] 985 = {File -> ..., FileType -> File, ByteCount -> 142286, Date -> ...} 986 987 #> FileInformation["ExampleData/missing_file.jpg"] 988 = {} 989 """ 990 991 rules = { 992 "FileInformation[name_String]": "If[FileExistsQ[name], {File -> ExpandFileName[name], FileType -> FileType[name], ByteCount -> FileByteCount[name], Date -> AbsoluteTime[FileDate[name]]}, {}]", 993 } 994 995 996class FileNameDepth(Builtin): 997 """ 998 <dl> 999 <dt>'FileNameDepth["$name$"]' 1000 <dd>gives the number of path parts in the given filename. 1001 </dl> 1002 1003 >> FileNameDepth["a/b/c"] 1004 = 3 1005 1006 >> FileNameDepth["a/b/c/"] 1007 = 3 1008 1009 #> FileNameDepth[x] 1010 = FileNameDepth[x] 1011 1012 #> FileNameDepth[$RootDirectory] 1013 = 0 1014 """ 1015 1016 attributes = "Protected" 1017 1018 options = { 1019 "OperatingSystem": "$OperatingSystem", 1020 } 1021 1022 rules = { 1023 "FileNameDepth[name_String]": "Length[FileNameSplit[name]]", 1024 } 1025 1026 1027class FileNameJoin(Builtin): 1028 """ 1029 <dl> 1030 <dt>'FileNameJoin[{"$dir_1$", "$dir_2$", ...}]' 1031 <dd>joins the $dir_i$ together into one path. 1032 1033 <dt>'FileNameJoin[..., OperatingSystem->"os"]' 1034 <dd>yields a file name in the format for the specified operating system. Possible choices are "Windows", "MacOSX", and "Unix". 1035 </dl> 1036 1037 >> FileNameJoin[{"dir1", "dir2", "dir3"}] 1038 = ... 1039 1040 >> FileNameJoin[{"dir1", "dir2", "dir3"}, OperatingSystem -> "Unix"] 1041 = dir1/dir2/dir3 1042 1043 >> FileNameJoin[{"dir1", "dir2", "dir3"}, OperatingSystem -> "Windows"] 1044 = dir1\\dir2\\dir3 1045 """ 1046 1047 attributes = "Protected" 1048 1049 options = { 1050 "OperatingSystem": "$OperatingSystem", 1051 } 1052 1053 messages = { 1054 "ostype": ( 1055 "The value of option OperatingSystem -> `1` " 1056 'must be one of "MacOSX", "Windows", or "Unix".' 1057 ), 1058 } 1059 1060 def apply(self, pathlist, evaluation, options): 1061 "FileNameJoin[pathlist_?ListQ, OptionsPattern[FileNameJoin]]" 1062 1063 py_pathlist = pathlist.to_python() 1064 if not all(isinstance(p, str) and p[0] == p[-1] == '"' for p in py_pathlist): 1065 return 1066 py_pathlist = [p[1:-1] for p in py_pathlist] 1067 1068 operating_system = ( 1069 options["System`OperatingSystem"].evaluate(evaluation).get_string_value() 1070 ) 1071 1072 if operating_system not in ["MacOSX", "Windows", "Unix"]: 1073 evaluation.message( 1074 "FileNameSplit", "ostype", options["System`OperatingSystem"] 1075 ) 1076 if os.name == "posix": 1077 operating_system = "Unix" 1078 elif os.name == "nt": 1079 operating_system = "Windows" 1080 elif os.name == "os2": 1081 operating_system = "MacOSX" 1082 else: 1083 return 1084 1085 if operating_system in ("Unix", "MacOSX"): 1086 import posixpath 1087 1088 result = posixpath.join(*py_pathlist) 1089 elif operating_system in ("Windows",): 1090 import ntpath 1091 1092 result = ntpath.join(*py_pathlist) 1093 else: 1094 result = osp.join(*py_pathlist) 1095 1096 return from_python(result) 1097 1098 1099class FileType(Builtin): 1100 """ 1101 <dl> 1102 <dt>'FileType["$file$"]' 1103 <dd>gives the type of a file, a string. This is typically 'File', 'Directory' or 'None'. 1104 </dl> 1105 1106 >> FileType["ExampleData/sunflowers.jpg"] 1107 = File 1108 >> FileType["ExampleData"] 1109 = Directory 1110 >> FileType["ExampleData/nonexistant"] 1111 = None 1112 1113 #> FileType[x] 1114 : File specification x is not a string of one or more characters. 1115 = FileType[x] 1116 """ 1117 1118 messages = { 1119 "fstr": ( 1120 "File specification `1` is not a string of " "one or more characters." 1121 ), 1122 } 1123 1124 attributes = "Protected" 1125 1126 def apply(self, filename, evaluation): 1127 "FileType[filename_]" 1128 if not isinstance(filename, String): 1129 evaluation.message("FileType", "fstr", filename) 1130 return 1131 path = filename.to_python()[1:-1] 1132 1133 path = path_search(path) 1134 1135 if path is None: 1136 return Symbol("None") 1137 1138 if osp.isfile(path): 1139 return Symbol("File") 1140 else: 1141 return Symbol("Directory") 1142 1143 1144class FindFile(Builtin): 1145 """ 1146 <dl> 1147 <dt>'FindFile[$name$]' 1148 <dd>searches '$Path' for the given filename. 1149 </dl> 1150 1151 >> FindFile["ExampleData/sunflowers.jpg"] 1152 = ... 1153 1154 >> FindFile["VectorAnalysis`"] 1155 = ... 1156 1157 >> FindFile["VectorAnalysis`VectorAnalysis`"] 1158 = ... 1159 1160 #> FindFile["SomeTypoPackage`"] 1161 = $Failed 1162 """ 1163 1164 attributes = "Protected" 1165 1166 messages = { 1167 "string": "String expected at position 1 in `1`.", 1168 } 1169 1170 def apply(self, name, evaluation): 1171 "FindFile[name_]" 1172 1173 py_name = name.to_python() 1174 1175 if not (isinstance(py_name, str) and py_name[0] == py_name[-1] == '"'): 1176 evaluation.message("FindFile", "string", Expression("FindFile", name)) 1177 return 1178 py_name = py_name[1:-1] 1179 1180 result = path_search(py_name) 1181 1182 if result is None: 1183 return SymbolFailed 1184 1185 return String(osp.abspath(result)) 1186 1187 1188class FileNames(Builtin): 1189 r""" 1190 <dl> 1191 <dt>'FileNames[]' 1192 <dd>Returns a list with the filenames in the current working folder. 1193 <dt>'FileNames[$form$]' 1194 <dd>Returns a list with the filenames in the current working folder that matches with $form$. 1195 <dt>'FileNames[{$form_1$, $form_2$, ...}]' 1196 <dd>Returns a list with the filenames in the current working folder that matches with one of $form_1$, $form_2$, .... 1197 <dt>'FileNames[{$form_1$, $form_2$, ...},{$dir_1$, $dir_2$, ...}]' 1198 <dd>Looks into the directories $dir_1$, $dir_2$, .... 1199 <dt>'FileNames[{$form_1$, $form_2$, ...},{$dir_1$, $dir_2$, ...}]' 1200 <dd>Looks into the directories $dir_1$, $dir_2$, .... 1201 <dt>'FileNames[{$forms$, $dirs$, $n$]' 1202 <dd>Look for files up to the level $n$. 1203 </dl> 1204 1205 >> SetDirectory[$InstallationDirectory <> "/autoload"]; 1206 >> FileNames["*.m", "formats"]//Length 1207 = 0 1208 >> FileNames["*.m", "formats", 3]//Length 1209 = 13 1210 >> FileNames["*.m", "formats", Infinity]//Length 1211 = 13 1212 """ 1213 # >> FileNames[]//Length 1214 # = 2 1215 fmtmaps = {Symbol("System`All"): "*"} 1216 options = { 1217 "IgnoreCase": "Automatic", 1218 } 1219 1220 messages = { 1221 "nofmtstr": "`1` is not a format or a list of formats.", 1222 "nodirstr": "`1` is not a directory name or a list of directory names.", 1223 "badn": "`1` is not an integer number.", 1224 } 1225 1226 def apply_0(self, evaluation, **options): 1227 """FileNames[OptionsPattern[FileNames]]""" 1228 return self.apply_3( 1229 String("*"), String(os.getcwd()), None, evaluation, **options 1230 ) 1231 1232 def apply_1(self, forms, evaluation, **options): 1233 """FileNames[forms_, OptionsPattern[FileNames]]""" 1234 return self.apply_3(forms, String(os.getcwd()), None, evaluation, **options) 1235 1236 def apply_2(self, forms, paths, evaluation, **options): 1237 """FileNames[forms_, paths_, OptionsPattern[FileNames]]""" 1238 return self.apply_3(forms, paths, None, evaluation, **options) 1239 1240 def apply_3(self, forms, paths, n, evaluation, **options): 1241 """FileNames[forms_, paths_, n_, OptionsPattern[FileNames]]""" 1242 filenames = set() 1243 # Building a list of forms 1244 if forms.get_head_name() == "System`List": 1245 str_forms = [] 1246 for p in forms._leaves: 1247 if self.fmtmaps.get(p, None): 1248 str_forms.append(self.fmtmaps[p]) 1249 else: 1250 str_forms.append(p) 1251 else: 1252 str_forms = [ 1253 self.fmtmaps[forms] if self.fmtmaps.get(forms, None) else forms 1254 ] 1255 # Building a list of directories 1256 if paths.get_head_name() == "System`String": 1257 str_paths = [paths.value] 1258 elif paths.get_head_name() == "System`List": 1259 str_paths = [] 1260 for p in paths._leaves: 1261 if p.get_head_name() == "System`String": 1262 str_paths.append(p.value) 1263 else: 1264 evaluation.message("FileNames", "nodirstr", paths) 1265 return 1266 else: 1267 evaluation.message("FileNames", "nodirstr", paths) 1268 return 1269 1270 if n is not None: 1271 if n.get_head_name() == "System`Integer": 1272 n = n.get_int_value() 1273 elif n.get_head_name() == "System`DirectedInfinity": 1274 n = None 1275 else: 1276 evaluation.message("FileNames", "badn", n) 1277 return 1278 else: 1279 n = 1 1280 1281 # list the files 1282 if options.get("System`IgnoreCase", None) == SymbolTrue: 1283 patterns = [ 1284 re.compile( 1285 "^" + to_regex(p, evaluation, abbreviated_patterns=True), 1286 re.IGNORECASE, 1287 ) 1288 + "$" 1289 for p in str_forms 1290 ] 1291 else: 1292 patterns = [ 1293 re.compile( 1294 "^" + to_regex(p, evaluation, abbreviated_patterns=True) + "$" 1295 ) 1296 for p in str_forms 1297 ] 1298 1299 for path in str_paths: 1300 if not osp.isdir(path): 1301 continue 1302 if n == 1: 1303 for fn in os.listdir(path): 1304 fullname = osp.join(path, fn) 1305 for pattern in patterns: 1306 if pattern.match(fn): 1307 filenames.add(fullname) 1308 break 1309 else: 1310 pathlen = len(path) 1311 for root, dirs, files in os.walk(path): 1312 # FIXME: This is an ugly and inefficient way 1313 # to avoid looking deeper than the level n, but I do not realize 1314 # how to do this better without a lot of code... 1315 if n is not None and len(root[pathlen:].split(osp.sep)) > n: 1316 continue 1317 for fn in files + dirs: 1318 for pattern in patterns: 1319 if pattern.match(fn): 1320 filenames.add(osp.join(root, fn)) 1321 break 1322 1323 return Expression("List", *[String(s) for s in sorted(filenames)]) 1324 1325 1326class FileNameSplit(Builtin): 1327 """ 1328 <dl> 1329 <dt>'FileNameSplit["$filenams$"]' 1330 <dd>splits a $filename$ into a list of parts. 1331 </dl> 1332 1333 >> FileNameSplit["example/path/file.txt"] 1334 = {example, path, file.txt} 1335 1336 #> FileNameSplit["example/path", OperatingSystem -> x] 1337 : The value of option OperatingSystem -> x must be one of "MacOSX", "Windows", or "Unix". 1338 = {example, path} 1339 """ 1340 1341 attributes = "Protected" 1342 1343 options = { 1344 "OperatingSystem": "$OperatingSystem", 1345 } 1346 1347 messages = { 1348 "ostype": ( 1349 "The value of option OperatingSystem -> `1` " 1350 'must be one of "MacOSX", "Windows", or "Unix".' 1351 ), 1352 } 1353 1354 def apply(self, filename, evaluation, options): 1355 "FileNameSplit[filename_String, OptionsPattern[FileNameSplit]]" 1356 1357 path = filename.to_python()[1:-1] 1358 1359 operating_system = ( 1360 options["System`OperatingSystem"].evaluate(evaluation).to_python() 1361 ) 1362 1363 if operating_system not in ['"MacOSX"', '"Windows"', '"Unix"']: 1364 evaluation.message( 1365 "FileNameSplit", "ostype", options["System`OperatingSystem"] 1366 ) 1367 if os.name == "posix": 1368 operating_system = "Unix" 1369 elif os.name == "nt": 1370 operating_system = "Windows" 1371 elif os.name == "os2": 1372 operating_system = "MacOSX" 1373 else: 1374 return 1375 1376 # TODO Implement OperatingSystem Option 1377 1378 result = [] 1379 while path not in ["", SYS_ROOT_DIR]: 1380 path, ext = osp.split(path) 1381 if ext != "": 1382 result.insert(0, ext) 1383 1384 return from_python(result) 1385 1386 1387class FileNameTake(Builtin): 1388 """ 1389 <dl> 1390 <dt>'FileNameTake["$file$"]' 1391 <dd>returns the last path element in the file name $name$. 1392 1393 <dt>'FileNameTake["$file$", $n$]' 1394 <dd>returns the first $n$ path elements in the file name $name$. 1395 1396 <dt>'FileNameTake["$file$", $-n$]' 1397 <dd>returns the last $n$ path elements in the file name $name$. 1398 </dl> 1399 1400 """ 1401 1402 # mmatura: please put in a pytest 1403 # >> FileNameTake["/tmp/file.txt"] 1404 # = file.txt 1405 # >> FileNameTake["tmp/file.txt", 1] 1406 # = tmp 1407 # >> FileNameTake["tmp/file.txt", -1] 1408 # = file.txt 1409 1410 attributes = "Protected" 1411 1412 options = { 1413 "OperatingSystem": "$OperatingSystem", 1414 } 1415 1416 def apply(self, filename, evaluation, options): 1417 "FileNameTake[filename_String, OptionsPattern[FileBaseName]]" 1418 path = pathlib.Path(filename.to_python()[1:-1]) 1419 return from_python(path.name) 1420 1421 def apply_n(self, filename, n, evaluation, options): 1422 "FileNameTake[filename_String, n_Integer, OptionsPattern[FileBaseName]]" 1423 n_int = n.get_int_value() 1424 parts = pathlib.Path(filename.to_python()[1:-1]).parts 1425 if n_int >= 0: 1426 subparts = parts[:n_int] 1427 else: 1428 subparts = parts[n_int:] 1429 return from_python(str(pathlib.PurePath(*subparts))) 1430 1431 1432class FindList(Builtin): 1433 """ 1434 <dl> 1435 <dt>'FindList[$file$, $text$]' 1436 <dd>returns a list of all lines in $file$ that contain $text$. 1437 <dt>'FindList[$file$, {$text1$, $text2$, ...}]' 1438 <dd>returns a list of all lines in $file$ that contain any of the specified string. 1439 <dt>'FindList[{$file1$, $file2$, ...}, ...]' 1440 <dd>returns a list of all lines in any of the $filei$ that contain the specified strings. 1441 </dl> 1442 1443 >> stream = FindList["ExampleData/EinsteinSzilLetter.txt", "uranium"]; 1444 #> Length[stream] 1445 = 7 1446 1447 >> FindList["ExampleData/EinsteinSzilLetter.txt", "uranium", 1] 1448 = {in manuscript, leads me to expect that the element uranium may be turned into} 1449 1450 #> FindList["ExampleData/EinsteinSzilLetter.txt", "project"] 1451 = {} 1452 1453 #> FindList["ExampleData/EinsteinSzilLetter.txt", "uranium", 0] 1454 = $Failed 1455 """ 1456 1457 messages = { 1458 "strs": "String or non-empty list of strings expected at position `1` in `2`.", 1459 "intnm": "Non-negative machine-sized integer expected at position `1` in `2`.", 1460 } 1461 1462 attributes = "Protected" 1463 1464 options = { 1465 "AnchoredSearch": "False", 1466 "IgnoreCase": "False", 1467 "RecordSeparators": '{"\r\n", "\n", "\r"}', 1468 "WordSearch": "False", 1469 "WordSeparators": '{" ", "\t"}', 1470 } 1471 1472 # TODO: Extra options AnchoredSearch, IgnoreCase RecordSeparators, 1473 # WordSearch, WordSeparators this is probably best done with a regex 1474 1475 def apply_without_n(self, filename, text, evaluation, options): 1476 "FindList[filename_, text_, OptionsPattern[FindList]]" 1477 return self.apply(filename, text, None, evaluation, options) 1478 1479 def apply(self, filename, text, n, evaluation, options): 1480 "FindList[filename_, text_, n_, OptionsPattern[FindList]]" 1481 py_text = text.to_python() 1482 py_name = filename.to_python() 1483 if n is None: 1484 py_n = None 1485 expr = Expression("FindList", filename, text) 1486 else: 1487 py_n = n.to_python() 1488 expr = Expression("FindList", filename, text, n) 1489 1490 if not isinstance(py_text, list): 1491 py_text = [py_text] 1492 1493 if not isinstance(py_name, list): 1494 py_name = [py_name] 1495 1496 if not all(isinstance(t, str) and t[0] == t[-1] == '"' for t in py_name): 1497 evaluation.message("FindList", "strs", "1", expr) 1498 return SymbolFailed 1499 1500 if not all(isinstance(t, str) and t[0] == t[-1] == '"' for t in py_text): 1501 evaluation.message("FindList", "strs", "2", expr) 1502 return SymbolFailed 1503 1504 if not ((isinstance(py_n, int) and py_n >= 0) or py_n is None): 1505 evaluation.message("FindList", "intnm", "3", expr) 1506 return SymbolFailed 1507 1508 if py_n == 0: 1509 return SymbolFailed 1510 1511 py_text = [t[1:-1] for t in py_text] 1512 py_name = [t[1:-1] for t in py_name] 1513 1514 results = [] 1515 for path in py_name: 1516 try: 1517 with mathics_open(path, "r") as f: 1518 lines = f.readlines() 1519 except IOError: 1520 evaluation.message("General", "noopen", path) 1521 return 1522 except MessageException as e: 1523 e.message(evaluation) 1524 return 1525 1526 result = [] 1527 for line in lines: 1528 for t in py_text: 1529 if line.find(t) != -1: 1530 result.append(line[:-1]) 1531 results.append(result) 1532 1533 results = [r for result in results for r in result] 1534 1535 if isinstance(py_n, int): 1536 results = results[: min(py_n, len(results))] 1537 1538 return from_python(results) 1539 1540 1541class HomeDirectory(Predefined): 1542 """ 1543 <dl> 1544 <dt>'$HomeDirectory' 1545 <dd>returns the users HOME directory. 1546 </dl> 1547 1548 >> $HomeDirectory 1549 = ... 1550 """ 1551 1552 name = "$HomeDirectory" 1553 1554 attributes = "Protected" 1555 1556 def evaluate(self, evaluation): 1557 global HOME_DIR 1558 return String(HOME_DIR) 1559 1560 1561class InitialDirectory(Predefined): 1562 """ 1563 <dl> 1564 <dt>'$InitialDirectory' 1565 <dd>returns the directory from which \\Mathics was started. 1566 </dl> 1567 1568 >> $InitialDirectory 1569 = ... 1570 """ 1571 1572 name = "$InitialDirectory" 1573 1574 def evaluate(self, evaluation): 1575 global INITIAL_DIR 1576 return String(INITIAL_DIR) 1577 1578 1579class InstallationDirectory(Predefined): 1580 """ 1581 <dl> 1582 <dt>'$InstallationDirectory' 1583 <dd>returns the top-level directory in which \\Mathics was installed. 1584 </dl> 1585 >> $InstallationDirectory 1586 = ... 1587 """ 1588 1589 attributes = ("Unprotected",) 1590 name = "$InstallationDirectory" 1591 1592 def evaluate(self, evaluation): 1593 global ROOT_DIR 1594 return String(ROOT_DIR) 1595 1596 1597class Needs(Builtin): 1598 """ 1599 <dl> 1600 <dt>'Needs["context`"]' 1601 <dd>loads the specified context if not already in '$Packages'. 1602 </dl> 1603 1604 >> Needs["VectorAnalysis`"] 1605 #> Needs["VectorAnalysis`"] 1606 1607 #> Needs["SomeFakePackageOrTypo`"] 1608 : Cannot open SomeFakePackageOrTypo`. 1609 : Context SomeFakePackageOrTypo` was not created when Needs was evaluated. 1610 = $Failed 1611 1612 #> Needs["VectorAnalysis"] 1613 : Invalid context specified at position 1 in Needs[VectorAnalysis]. A context must consist of valid symbol names separated by and ending with `. 1614 = Needs[VectorAnalysis] 1615 1616 ## --- VectorAnalysis --- 1617 1618 #> Needs["VectorAnalysis`"] 1619 1620 #> DotProduct[{1,2,3}, {4,5,6}] 1621 = 32 1622 #> DotProduct[{-1.4, 0.6, 0.2}, {0.1, 0.6, 1.7}] 1623 = 0.56 1624 1625 #> CrossProduct[{1,2,3}, {4,5,6}] 1626 = {-3, 6, -3} 1627 #> CrossProduct[{-1.4, 0.6, 0.2}, {0.1, 0.6, 1.7}] 1628 = {0.9, 2.4, -0.9} 1629 1630 #> ScalarTripleProduct[{-2,3,1},{0,4,0},{-1,3,3}] 1631 = -20 1632 #> ScalarTripleProduct[{-1.4,0.6,0.2}, {0.1,0.6,1.7}, {0.7,-1.5,-0.2}] 1633 = -2.79 1634 1635 #> CoordinatesToCartesian[{2, Pi, 3}, Spherical] 1636 = {0, 0, -2} 1637 #> CoordinatesFromCartesian[%, Spherical] 1638 = {2, Pi, 0} 1639 #> CoordinatesToCartesian[{2, Pi, 3}, Cylindrical] 1640 = {-2, 0, 3} 1641 #> CoordinatesFromCartesian[%, Cylindrical] 1642 = {2, Pi, 3} 1643 ## Needs Sin/Cos exact value (PR #100) for these tests to pass 1644 ## #> CoordinatesToCartesian[{2, Pi / 4, Pi / 3}, Spherical] 1645 ## = {Sqrt[2] / 2, Sqrt[6] / 2, Sqrt[2]} 1646 ## #> CoordinatesFromCartesian[%, Spherical] 1647 ## = {2, Pi / 4, Pi / 3} 1648 ## #> CoordinatesToCartesian[{2, Pi / 4, -1}, Cylindrical] 1649 ## = {Sqrt[2], Sqrt[2], -1} 1650 ## #> CoordinatesFromCartesian[%, Cylindrical] 1651 ## = {2, Pi / 4, -1} 1652 #> CoordinatesToCartesian[{0.27, 0.51, 0.92}, Cylindrical] 1653 = {0.235641, 0.131808, 0.92} 1654 #> CoordinatesToCartesian[{0.27, 0.51, 0.92}, Spherical] 1655 = {0.0798519, 0.104867, 0.235641} 1656 1657 #> Coordinates[] 1658 = {Xx, Yy, Zz} 1659 #> Coordinates[Spherical] 1660 = {Rr, Ttheta, Pphi} 1661 #> SetCoordinates[Cylindrical] 1662 = Cylindrical[Rr, Ttheta, Zz] 1663 #> Coordinates[] 1664 = {Rr, Ttheta, Zz} 1665 #> CoordinateSystem 1666 = Cylindrical 1667 #> Parameters[] 1668 = {} 1669 #> CoordinateRanges[] 1670 ## = {0 <= Rr < Infinity, -Pi < Ttheta <= Pi, -Infinity < Zz < Infinity} 1671 = {0 <= Rr && Rr < Infinity, -Pi < Ttheta && Ttheta <= Pi, -Infinity < Zz < Infinity} 1672 #> CoordinateRanges[Cartesian] 1673 = {-Infinity < Xx < Infinity, -Infinity < Yy < Infinity, -Infinity < Zz < Infinity} 1674 #> ScaleFactors[Cartesian] 1675 = {1, 1, 1} 1676 #> ScaleFactors[Spherical] 1677 = {1, Rr, Rr Sin[Ttheta]} 1678 #> ScaleFactors[Cylindrical] 1679 = {1, Rr, 1} 1680 #> ScaleFactors[{2, 1, 3}, Cylindrical] 1681 = {1, 2, 1} 1682 #> JacobianDeterminant[Cartesian] 1683 = 1 1684 #> JacobianDeterminant[Spherical] 1685 = Rr ^ 2 Sin[Ttheta] 1686 #> JacobianDeterminant[Cylindrical] 1687 = Rr 1688 #> JacobianDeterminant[{2, 1, 3}, Cylindrical] 1689 = 2 1690 #> JacobianMatrix[Cartesian] 1691 = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}} 1692 #> JacobianMatrix[Spherical] 1693 = {{Cos[Pphi] Sin[Ttheta], Rr Cos[Pphi] Cos[Ttheta], -Rr Sin[Pphi] Sin[Ttheta]}, {Sin[Pphi] Sin[Ttheta], Rr Cos[Ttheta] Sin[Pphi], Rr Cos[Pphi] Sin[Ttheta]}, {Cos[Ttheta], -Rr Sin[Ttheta], 0}} 1694 #> JacobianMatrix[Cylindrical] 1695 = {{Cos[Ttheta], -Rr Sin[Ttheta], 0}, {Sin[Ttheta], Rr Cos[Ttheta], 0}, {0, 0, 1}} 1696 """ 1697 1698 messages = { 1699 "ctx": ( 1700 "Invalid context specified at position `2` in `1`. " 1701 "A context must consist of valid symbol names separated by " 1702 "and ending with `3`." 1703 ), 1704 "nocont": "Context `1` was not created when Needs was evaluated.", 1705 } 1706 1707 def apply(self, context, evaluation): 1708 "Needs[context_String]" 1709 contextstr = context.get_string_value() 1710 if contextstr == "": 1711 return SymbolNull 1712 if contextstr[0] == "`": 1713 curr_ctxt = evaluation.definitions.get_current_context() 1714 contextstr = curr_ctxt + contextstr[1:] 1715 context = String(contextstr) 1716 if not valid_context_name(contextstr): 1717 evaluation.message("Needs", "ctx", Expression("Needs", context), 1, "`") 1718 return 1719 test_loaded = Expression("MemberQ", Symbol("$Packages"), context) 1720 test_loaded = test_loaded.evaluate(evaluation) 1721 if test_loaded.is_true(): 1722 # Already loaded 1723 return SymbolNull 1724 result = Expression("Get", context).evaluate(evaluation) 1725 1726 if result == SymbolFailed: 1727 evaluation.message("Needs", "nocont", context) 1728 return SymbolFailed 1729 1730 return SymbolNull 1731 1732 1733class OperatingSystem(Predefined): 1734 """ 1735 <dl> 1736 <dt>'$OperatingSystem' 1737 <dd>gives the type of operating system running Mathics. 1738 </dl> 1739 1740 >> $OperatingSystem 1741 = ... 1742 """ 1743 1744 attributes = ("Locked", "Protected") 1745 name = "$OperatingSystem" 1746 1747 def evaluate(self, evaluation): 1748 if os.name == "posix": 1749 return String("Unix") 1750 elif os.name == "nt": 1751 return String("Windows") 1752 elif os.name == "os2": 1753 return String("MacOSX") 1754 else: 1755 return String("Unknown") 1756 1757 1758class ParentDirectory(Builtin): 1759 """ 1760 <dl> 1761 <dt>'ParentDirectory[]' 1762 <dd>returns the parent of the current working directory. 1763 <dt>'ParentDirectory["$dir$"]' 1764 <dd>returns the parent $dir$. 1765 </dl> 1766 1767 >> ParentDirectory[] 1768 = ... 1769 """ 1770 1771 rules = { 1772 "ParentDirectory[]": "ParentDirectory[Directory[]]", 1773 } 1774 1775 messages = { 1776 "fstr": ( 1777 "File specification `1` is not a string of " "one or more characters." 1778 ), 1779 } 1780 1781 attributes = "Protected" 1782 1783 def apply(self, path, evaluation): 1784 "ParentDirectory[path_]" 1785 1786 if not isinstance(path, String): 1787 evaluation.message("ParentDirectory", "fstr", path) 1788 return 1789 1790 pypath = path.to_python()[1:-1] 1791 1792 result = osp.abspath(osp.join(pypath, osp.pardir)) 1793 return String(result) 1794 1795 1796class Path(Predefined): 1797 """ 1798 <dl> 1799 <dt>'$Path' 1800 <dd>returns the list of directories to search when looking for a file. 1801 </dl> 1802 1803 >> $Path 1804 = ... 1805 """ 1806 1807 attributes = ("Unprotected",) 1808 name = "$Path" 1809 1810 def evaluate(self, evaluation): 1811 return Expression(SymbolList, *[String(p) for p in PATH_VAR]) 1812 1813 1814class PathnameSeparator(Predefined): 1815 """ 1816 <dl> 1817 <dt>'$PathnameSeparator' 1818 <dd>returns a string for the seperator in paths. 1819 </dl> 1820 1821 >> $PathnameSeparator 1822 = ... 1823 """ 1824 1825 name = "$PathnameSeparator" 1826 1827 def evaluate(self, evaluation): 1828 return String(os.sep) 1829 1830 1831class RenameDirectory(Builtin): 1832 """ 1833 <dl> 1834 <dt>'RenameDirectory["$dir1$", "$dir2$"]' 1835 <dd>renames directory $dir1$ to $dir2$. 1836 </dl> 1837 """ 1838 1839 attributes = "Protected" 1840 1841 messages = { 1842 "argr": "called with `1` argument; 2 arguments are expected.", 1843 "fstr": ( 1844 "File specification `1` is not a string of " "one or more characters." 1845 ), 1846 "filex": "Cannot overwrite existing file `1`.", 1847 "nodir": "Directory `1` not found.", 1848 } 1849 1850 def apply(self, dirs, evaluation): 1851 "RenameDirectory[dirs__]" 1852 1853 seq = dirs.get_sequence() 1854 if len(seq) != 2: 1855 evaluation.message("RenameDirectory", "argr", len(seq)) 1856 return 1857 (dir1, dir2) = (s.to_python() for s in seq) 1858 1859 if not (isinstance(dir1, str) and dir1[0] == dir1[-1] == '"'): 1860 evaluation.message("RenameDirectory", "fstr", seq[0]) 1861 return 1862 dir1 = dir1[1:-1] 1863 1864 if not (isinstance(dir2, str) and dir2[0] == dir2[-1] == '"'): 1865 evaluation.message("RenameDirectory", "fstr", seq[1]) 1866 return 1867 dir2 = dir2[1:-1] 1868 1869 if not osp.isdir(dir1): 1870 evaluation.message("RenameDirectory", "nodir", seq[0]) 1871 return SymbolFailed 1872 if osp.isdir(dir2): 1873 evaluation.message("RenameDirectory", "filex", seq[1]) 1874 return SymbolFailed 1875 1876 shutil.move(dir1, dir2) 1877 1878 return String(osp.abspath(dir2)) 1879 1880 1881class RenameFile(Builtin): 1882 """ 1883 <dl> 1884 <dt>'RenameFile["$file1$", "$file2$"]' 1885 <dd>renames $file1$ to $file2$. 1886 </dl> 1887 1888 >> CopyFile["ExampleData/sunflowers.jpg", "MathicsSunflowers.jpg"] 1889 = MathicsSunflowers.jpg 1890 >> RenameFile["MathicsSunflowers.jpg", "MathicsSunnyFlowers.jpg"] 1891 = MathicsSunnyFlowers.jpg 1892 >> DeleteFile["MathicsSunnyFlowers.jpg"] 1893 """ 1894 1895 messages = { 1896 "filex": "Cannot overwrite existing file `1`.", 1897 "fstr": ( 1898 "File specification `1` is not a string of " "one or more characters." 1899 ), 1900 "nffil": "File not found during `1`.", 1901 } 1902 1903 attributes = "Protected" 1904 1905 def apply(self, source, dest, evaluation): 1906 "RenameFile[source_, dest_]" 1907 1908 py_source = source.to_python() 1909 py_dest = dest.to_python() 1910 1911 # Check filenames 1912 if not (isinstance(py_source, str) and py_source[0] == py_source[-1] == '"'): 1913 evaluation.message("RenameFile", "fstr", source) 1914 return 1915 if not (isinstance(py_dest, str) and py_dest[0] == py_dest[-1] == '"'): 1916 evaluation.message("RenameFile", "fstr", dest) 1917 return 1918 1919 py_source = py_source[1:-1] 1920 py_dest = py_dest[1:-1] 1921 1922 py_source = path_search(py_source) 1923 1924 if py_source is None: 1925 evaluation.message("RenameFile", "filex", source) 1926 return SymbolFailed 1927 1928 if osp.exists(py_dest): 1929 evaluation.message("RenameFile", "filex", dest) 1930 return SymbolFailed 1931 1932 try: 1933 shutil.move(py_source, py_dest) 1934 except IOError: 1935 evaluation.message("RenameFile", "nffil", dest) 1936 return SymbolFailed 1937 1938 return dest 1939 1940 1941class ResetDirectory(Builtin): 1942 """ 1943 <dl> 1944 <dt>'ResetDirectory[]' 1945 <dd>pops a directory from the directory stack and returns it. 1946 </dl> 1947 1948 >> ResetDirectory[] 1949 = ... 1950 """ 1951 1952 messages = { 1953 "dtop": "Directory stack is empty.", 1954 } 1955 1956 attributes = "Protected" 1957 1958 def apply(self, evaluation): 1959 "ResetDirectory[]" 1960 try: 1961 tmp = DIRECTORY_STACK.pop() 1962 except IndexError: 1963 tmp = os.getcwd() 1964 evaluation.message("ResetDirectory", "dtop") 1965 else: 1966 os.chdir(tmp) 1967 return String(tmp) 1968 1969 1970class RootDirectory(Predefined): 1971 """ 1972 <dl> 1973 <dt>'$RootDirectory' 1974 <dd>returns the system root directory. 1975 </dl> 1976 1977 >> $RootDirectory 1978 = ... 1979 """ 1980 1981 name = "$RootDirectory" 1982 1983 attributes = "Protected" 1984 1985 def evaluate(self, evaluation): 1986 global SYS_ROOT_DIR 1987 return String(SYS_ROOT_DIR) 1988 1989 1990class SetDirectory(Builtin): 1991 """ 1992 <dl> 1993 <dt>'SetDirectory[$dir$]' 1994 <dd>sets the current working directory to $dir$. 1995 </dl> 1996 1997 S> SetDirectory[] 1998 = ... 1999 2000 #> SetDirectory["MathicsNonExample"] 2001 : Cannot set current directory to MathicsNonExample. 2002 = $Failed 2003 """ 2004 2005 rules = { 2006 "SetDirectory[]": "SetDirectory[$HomeDirectory]", 2007 } 2008 2009 messages = { 2010 "fstr": ( 2011 "File specification `1` is not a string of " "one or more characters." 2012 ), 2013 "cdir": "Cannot set current directory to `1`.", 2014 } 2015 2016 attributes = "Protected" 2017 2018 def apply(self, path, evaluation): 2019 "SetDirectory[path_]" 2020 2021 if not isinstance(path, String): 2022 evaluation.message("SetDirectory", "fstr", path) 2023 return 2024 2025 py_path = path.__str__()[1:-1] 2026 2027 if py_path is None or not osp.isdir(py_path): 2028 evaluation.message("SetDirectory", "cdir", path) 2029 return SymbolFailed 2030 2031 try: 2032 os.chdir(py_path) 2033 except: 2034 return SymbolFailed 2035 2036 DIRECTORY_STACK.append(os.getcwd()) 2037 return String(os.getcwd()) 2038 2039 2040class SetFileDate(Builtin): 2041 """ 2042 <dl> 2043 <dt>'SetFileDate["$file$"]' 2044 <dd>set the file access and modification dates of $file$ to the current date. 2045 <dt>'SetFileDate["$file$", $date$]' 2046 <dd>set the file access and modification dates of $file$ to the specified date list. 2047 <dt>'SetFileDate["$file$", $date$, "$type$"]' 2048 <dd>set the file date of $file$ to the specified date list. 2049 The "$type$" can be one of "$Access$", "$Creation$", "$Modification$", or 'All'. 2050 </dl> 2051 2052 Create a temporary file (for example purposes) 2053 >> tmpfilename = $TemporaryDirectory <> "/tmp0"; 2054 >> Close[OpenWrite[tmpfilename]]; 2055 2056 >> SetFileDate[tmpfilename, {2002, 1, 1, 0, 0, 0.}, "Access"]; 2057 2058 >> FileDate[tmpfilename, "Access"] 2059 = {2002, 1, 1, 0, 0, 0.} 2060 2061 #> SetFileDate[tmpfilename, {2002, 1, 1, 0, 0, 0.}]; 2062 #> FileDate[tmpfilename, "Access"] 2063 = {2002, 1, 1, 0, 0, 0.} 2064 2065 #> SetFileDate[tmpfilename] 2066 #> FileDate[tmpfilename, "Access"] 2067 = {...} 2068 2069 #> DeleteFile[tmpfilename] 2070 2071 #> SetFileDate["MathicsNonExample"] 2072 : File not found during SetFileDate[MathicsNonExample]. 2073 = $Failed 2074 """ 2075 2076 messages = { 2077 "fstr": ( 2078 "File specification `1` is not a string of one or " "more characters." 2079 ), 2080 "nffil": "File not found during `1`.", 2081 "fdate": ( 2082 "Date specification should be either the number of seconds " 2083 "since January 1, 1900 or a {y, m, d, h, m, s} list." 2084 ), 2085 "datetype": ( 2086 'Date type a should be "Access", "Modification", ' 2087 '"Creation" (Windows only), or All.' 2088 ), 2089 "nocreationunix": ( 2090 "The Creation date of a file cannot be set on " "Macintosh or Unix." 2091 ), 2092 } 2093 2094 attributes = "Protected" 2095 2096 def apply(self, filename, datelist, attribute, evaluation): 2097 "SetFileDate[filename_, datelist_, attribute_]" 2098 2099 py_filename = filename.to_python() 2100 2101 if datelist is None: 2102 py_datelist = Expression("DateList").evaluate(evaluation).to_python() 2103 expr = Expression("SetFileDate", filename) 2104 else: 2105 py_datelist = datelist.to_python() 2106 2107 if attribute is None: 2108 py_attr = "All" 2109 if datelist is not None: 2110 expr = Expression("SetFileDate", filename, datelist) 2111 else: 2112 py_attr = attribute.to_python() 2113 expr = Expression("SetFileDate", filename, datelist, attribute) 2114 2115 # Check filename 2116 if not ( 2117 isinstance(py_filename, str) and py_filename[0] == py_filename[-1] == '"' 2118 ): 2119 evaluation.message("SetFileDate", "fstr", filename) 2120 return 2121 py_filename = path_search(py_filename[1:-1]) 2122 2123 if py_filename is None: 2124 evaluation.message("SetFileDate", "nffil", expr) 2125 return SymbolFailed 2126 2127 # Check datelist 2128 if not ( 2129 isinstance(py_datelist, list) 2130 and len(py_datelist) == 6 2131 and all(isinstance(d, int) for d in py_datelist[:-1]) 2132 and isinstance(py_datelist[-1], float) 2133 ): 2134 evaluation.message("SetFileDate", "fdate", expr) 2135 2136 # Check attribute 2137 if py_attr not in ['"Access"', '"Creation"', '"Modification"', "All"]: 2138 evaluation.message("SetFileDate", "datetype") 2139 return 2140 2141 epochtime = ( 2142 Expression("AbsoluteTime", time.strftime("%Y-%m-%d %H:%M", time.gmtime(0))) 2143 .evaluate(evaluation) 2144 .to_python() 2145 ) 2146 2147 stattime = Expression("AbsoluteTime", from_python(py_datelist)) 2148 stattime = stattime.to_python(n_evaluation=evaluation) 2149 2150 stattime -= epochtime 2151 2152 try: 2153 os.stat(py_filename) 2154 if py_attr == '"Access"': 2155 os.utime(py_filename, (stattime, osp.getatime(py_filename))) 2156 if py_attr == '"Creation"': 2157 if os.name == "posix": 2158 evaluation.message("SetFileDate", "nocreationunix") 2159 return SymbolFailed 2160 else: 2161 # TODO: Note: This is windows only 2162 return SymbolFailed 2163 if py_attr == '"Modification"': 2164 os.utime(py_filename, (osp.getatime(py_filename), stattime)) 2165 if py_attr == "All": 2166 os.utime(py_filename, (stattime, stattime)) 2167 except OSError as e: 2168 # evaluation.message(...) 2169 return SymbolFailed 2170 2171 return SymbolNull 2172 2173 def apply_1arg(self, filename, evaluation): 2174 "SetFileDate[filename_]" 2175 return self.apply(filename, None, None, evaluation) 2176 2177 def apply_2arg(self, filename, datelist, evaluation): 2178 "SetFileDate[filename_, datelist_]" 2179 return self.apply(filename, datelist, None, evaluation) 2180 2181 2182class TemporaryDirectory(Predefined): 2183 """ 2184 <dl> 2185 <dt>'$TemporaryDirectory' 2186 <dd>returns the directory used for temporary files. 2187 </dl> 2188 2189 >> $TemporaryDirectory 2190 = ... 2191 """ 2192 2193 name = "$TemporaryDirectory" 2194 2195 def evaluate(self, evaluation): 2196 return String(TMP_DIR) 2197 2198 2199class ToFileName(Builtin): 2200 """ 2201 <dl> 2202 <dt>'ToFileName[{"$dir_1$", "$dir_2$", ...}]' 2203 <dd>joins the $dir_i$ together into one path. 2204 </dl> 2205 2206 'ToFileName' has been superseded by 'FileNameJoin'. 2207 2208 >> ToFileName[{"dir1", "dir2"}, "file"] 2209 = dir1...dir2...file 2210 2211 >> ToFileName["dir1", "file"] 2212 = dir1...file 2213 2214 >> ToFileName[{"dir1", "dir2", "dir3"}] 2215 = dir1...dir2...dir3 2216 """ 2217 2218 rules = { 2219 "ToFileName[dir_String, name_String]": "FileNameJoin[{dir, name}]", 2220 "ToFileName[dirs_?ListQ, name_String]": "FileNameJoin[Append[dirs, name]]", 2221 "ToFileName[dirs_?ListQ]": "FileNameJoin[dirs]", 2222 } 2223 2224 2225class UserBaseDirectory(Predefined): 2226 """ 2227 <dl> 2228 <dt>'$UserBaseDirectory' 2229 <dd>returns the folder where user configurations are stored. 2230 </dl> 2231 2232 >> $RootDirectory 2233 = ... 2234 """ 2235 2236 name = "$UserBaseDirectory" 2237 2238 attributes = "Protected" 2239 2240 def evaluate(self, evaluation): 2241 global HOME_DIR 2242 return String(HOME_DIR + os.sep + ".mathics") 2243 2244 2245class URLSave(Builtin): 2246 """ 2247 <dl> 2248 <dt>'URLSave["url"]' 2249 <dd>Save "url" in a temporary file. 2250 <dt>'URLSave["url", $filename$]' 2251 <dd>Save "url" in $filename$. 2252 </dl> 2253 """ 2254 2255 messages = { 2256 "invfile": "`1` is not a valid Filename", 2257 "invhttp": "`1` is not a valid URL", 2258 } 2259 2260 def apply_1(self, url, evaluation, **options): 2261 "URLSave[url_String, OptionsPattern[URLSave]]" 2262 return self.apply_2(url, None, evaluation, **options) 2263 2264 def apply_2(self, url, filename, evaluation, **options): 2265 "URLSave[url_String, filename_, OptionsPattern[URLSave]]" 2266 url = url.value 2267 if filename is None: 2268 result = urlsave_tmp(url, None, **options) 2269 elif filename.get_head_name() == "String": 2270 filename = filename.value 2271 result = urlsave_tmp(url, filename, **options) 2272 else: 2273 evaluation.message("URLSave", "invfile", filename) 2274 return SymbolFailed 2275 if result is None: 2276 return SymbolFailed 2277 return String(result) 2278 2279 2280# To placate import 2281ROOT_DIR, HOME_DIR 2282