1919import requests
2020import pathlib
2121
22+ from io import BytesIO , StringIO
2223import os .path as osp
2324from itertools import chain
2425
3738 SymbolFalse ,
3839 SymbolNull ,
3940 SymbolTrue ,
41+ SymbolInfinity ,
4042 from_python ,
4143 Integer ,
4244 BoxError ,
4749from mathics .core .numbers import dps
4850from mathics .builtin .base import Builtin , Predefined , BinaryOperator , PrefixOperator
4951from mathics .builtin .numeric import Hash
50- from mathics .builtin .strings import to_python_encoding
52+ from mathics .builtin .strings import to_python_encoding , to_regex
5153from mathics .builtin .base import MessageException
5254from mathics .settings import ROOT_DIR
53-
55+ import re
5456
5557INITIAL_DIR = os .getcwd ()
5658HOME_DIR = osp .expanduser ("~" )
6264PATH_VAR = ["." , HOME_DIR , osp .join (ROOT_DIR , "data" ), osp .join (ROOT_DIR , "packages" )]
6365
6466
67+ def create_temporary_file (suffix = None , delete = False ):
68+ if suffix == "" :
69+ suffix = None
70+
71+ fp = tempfile .NamedTemporaryFile (delete = delete , suffix = suffix )
72+ result = fp .name
73+ fp .close ()
74+ return result
75+
76+ def urlsave_tmp (url , location = None , ** kwargs ):
77+ suffix = ""
78+ strip_url = url .split ("/" )
79+ if len (strip_url ) > 3 :
80+ strip_url = strip_url [- 1 ]
81+ if strip_url != "" :
82+ suffix = strip_url [len (strip_url .split ("." )[0 ]) :]
83+ try :
84+ r = requests .get (url , allow_redirects = True )
85+ if location is None :
86+ location = create_temporary_file (suffix = suffix )
87+ with open (location , "wb" ) as fp :
88+ fp .write (r .content )
89+ result = fp .name
90+ except Exception :
91+ result = None
92+ return result
93+
94+
6595def path_search (filename ):
6696 # For names of the form "name`", search for name.mx and name.m
6797 if filename [- 1 ] == "`" :
@@ -81,23 +111,7 @@ def path_search(filename):
81111 or (lenfn > 8 and filename [:8 ] == "https://" )
82112 or (lenfn > 6 and filename [:6 ] == "ftp://" )
83113 ):
84- suffix = ""
85- strip_filename = filename .split ("/" )
86- if len (strip_filename ) > 3 :
87- strip_filename = strip_filename [- 1 ]
88- if strip_filename != "" :
89- suffix = strip_filename [len (strip_filename .split ("." )[0 ]) :]
90- try :
91- r = requests .get (filename , allow_redirects = True )
92- if suffix != "" :
93- fp = tempfile .NamedTemporaryFile (delete = False , suffix = suffix )
94- else :
95- fp = tempfile .NamedTemporaryFile (delete = False )
96- fp .write (r .content )
97- result = fp .name
98- fp .close ()
99- except Exception :
100- result = None
114+ result = urlsave_tmp (filename )
101115 else :
102116 for p in PATH_VAR + ["" ]:
103117 path = osp .join (p , filename )
@@ -1947,8 +1961,10 @@ def apply(self, channel, expr, evaluation):
19471961 Expression ("FullForm" , result ).evaluate (evaluation ),
19481962 )
19491963 exprs .append (result )
1950-
1951- stream .write ("" .join (exprs ))
1964+ line = "" .join (exprs )
1965+ if type (stream ) is BytesIO :
1966+ line = line .encode ('utf8' )
1967+ stream .write (line )
19521968 try :
19531969 stream .flush ()
19541970 except IOError as err :
@@ -4942,3 +4958,211 @@ def apply(self, context, evaluation):
49424958 return SymbolFailed
49434959
49444960 return SymbolNull
4961+
4962+
4963+ class URLSave (Builtin ):
4964+ """
4965+ <dl>
4966+ <dt>'URLSave["url"]'
4967+ <dd>Save "url" in a temporary file.
4968+ <dt>'URLSave["url", $filename$]'
4969+ <dd>Save "url" in $filename$.
4970+ </dl>
4971+ """
4972+ messages = {"invfile" : '`1` is not a valid Filename' ,
4973+ "invhttp" : '`1` is not a valid URL'
4974+ }
4975+ def apply_1 (self , url , evaluation , ** options ):
4976+ 'URLSave[url_String, OptionsPattern[URLSave]]'
4977+ return self .apply_2 (url , None , evaluation , ** options )
4978+
4979+ def apply_2 (self , url , filename , evaluation , ** options ):
4980+ 'URLSave[url_String, filename_, OptionsPattern[URLSave]]'
4981+ url = url .value
4982+ if filename is None :
4983+ result = urlsave_tmp (url , None , ** options )
4984+ elif filename .get_head_name ()== "String" :
4985+ filename = filename .value
4986+ result = urlsave_tmp (url , filename , ** options )
4987+ else :
4988+ evaluation .message ("URLSave" , "invfile" , filename )
4989+ return SymbolFailed
4990+ if result is None :
4991+ return SymbolFailed
4992+ return String (result )
4993+
4994+
4995+ class CreateFile (Builtin ):
4996+ """
4997+ <dl>
4998+ <dt>'CreateFile["filename"]'
4999+ <dd>Creates a file named "filename" temporary file, but do not open it.
5000+ <dt>'CreateFile[]'
5001+ <dd>Creates a temporary file, but do not open it.
5002+ </dl>
5003+ """
5004+ rules = {'CreateFile[]' :'CreateTemporary[]' ,}
5005+ options = {'CreateIntermediateDirectories' : 'True' ,
5006+ 'OverwriteTarget' : 'True' ,
5007+ }
5008+
5009+ def apply_1 (self , filename , evaluation , ** options ):
5010+ 'CreateFile[filename_String, OptionsPattern[CreateFile]]'
5011+ try :
5012+ # TODO: Implement options
5013+ if not osp .isfile (filename .value ):
5014+ f = open (filename .value , "w" )
5015+ res = f .name
5016+ f .close ()
5017+ return String (res )
5018+ else :
5019+ return filename
5020+ except :
5021+ return SymbolFailed
5022+
5023+ class CreateTemporary (Builtin ):
5024+ """
5025+ <dl>
5026+ <dt>'CreateTemporary[]'
5027+ <dd>Creates a temporary file, but do not open it.
5028+ </dl>
5029+ """
5030+ def apply_0 (self , evaluation ):
5031+ 'CreateTemporary[]'
5032+ try :
5033+ res = create_temporary_file ()
5034+ except :
5035+ return SymbolFailed
5036+ return String (res )
5037+
5038+
5039+ class FileNames (Builtin ):
5040+ """
5041+ <dl>
5042+ <dt>'FileNames[]'
5043+ <dd>Returns a list with the filenames in the current working folder.
5044+ <dt>'FileNames[$form$]'
5045+ <dd>Returns a list with the filenames in the current working folder that matches with $form$.
5046+ <dt>'FileNames[{$form_1$, $form_2$, $\ldots$}]'
5047+ <dd>Returns a list with the filenames in the current working folder that matches with one of $form_1$, $form_2$, $\ldots$.
5048+ <dt>'FileNames[{$form_1$, $form_2$, $\ldots$},{$dir_1$, $dir_2$, $\ldots$}]'
5049+ <dd>Looks into the directories $dir_1$, $dir_2$, $\ldots$.
5050+ <dt>'FileNames[{$form_1$, $form_2$, $\ldots$},{$dir_1$, $dir_2$, $\ldots$}]'
5051+ <dd>Looks into the directories $dir_1$, $dir_2$, $\ldots$.
5052+ <dt>'FileNames[{$forms$, $dirs$, $n$]'
5053+ <dd>Look for files up to the level $n$.
5054+ </dl>
5055+
5056+ >> SetDirectory[$InstallationDirectory <> "/autoload"];
5057+ >> FileNames[]//Length
5058+ = 2
5059+ >> FileNames["*.m", "formats"]//Length
5060+ = 0
5061+ >> FileNames["*.m", "formats", 3]//Length
5062+ = 12
5063+ >> FileNames["*.m", "formats", Infinity]//Length
5064+ = 12
5065+ """
5066+ fmtmaps = {Symbol ("System`All" ): "*" }
5067+ options = {"IgnoreCase" : "Automatic" ,}
5068+
5069+ messages = {
5070+ "nofmtstr" : "`1` is not a format or a list of formats." ,
5071+ "nodirstr" : "`1` is not a directory name or a list of directory names." ,
5072+ "badn" : "`1` is not an integer number." ,
5073+ }
5074+
5075+ def apply_0 (self , evaluation , ** options ):
5076+ '''FileNames[OptionsPattern[FileNames]]'''
5077+ return self .apply_3 (String ("*" ), String (os .getcwd ()), None , evaluation , ** options )
5078+
5079+ def apply_1 (self , forms , evaluation , ** options ):
5080+ '''FileNames[forms_, OptionsPattern[FileNames]]'''
5081+ return self .apply_3 (forms , String (os .getcwd ()), None , evaluation , ** options )
5082+
5083+ def apply_2 (self , forms , paths , evaluation , ** options ):
5084+ '''FileNames[forms_, paths_, OptionsPattern[FileNames]]'''
5085+ return self .apply_3 (forms , paths , None , evaluation , ** options )
5086+
5087+ def apply_3 (self , forms , paths , n , evaluation , ** options ):
5088+ '''FileNames[forms_, paths_, n_, OptionsPattern[FileNames]]'''
5089+ filenames = set ()
5090+ # Building a list of forms
5091+ if forms .get_head_name () == "System`List" :
5092+ str_forms = []
5093+ for p in forms ._leaves :
5094+ if self .fmtmaps .get (p , None ):
5095+ str_forms .append (self .fmtmaps [p ])
5096+ else :
5097+ str_forms .append (p )
5098+ else :
5099+ str_forms = [self .fmtmaps [forms ]
5100+ if self .fmtmaps .get (forms , None )
5101+ else forms ]
5102+ # Building a list of directories
5103+ if paths .get_head_name () == "System`String" :
5104+ str_paths = [paths .value ]
5105+ elif paths .get_head_name () == "System`List" :
5106+ str_paths = []
5107+ for p in paths ._leaves :
5108+ if p .get_head_name () == "System`String" :
5109+ str_paths .append (p .value )
5110+ else :
5111+ evaluation .message ("FileNames" , "nodirstr" , paths )
5112+ return
5113+ else :
5114+ evaluation .message ("FileNames" , "nodirstr" , paths )
5115+ return
5116+
5117+ if n is not None :
5118+ if n .get_head_name () == "System`Integer" :
5119+ n = n .get_int_value ()
5120+ elif n .get_head_name () == "System`DirectedInfinity" :
5121+ n = None
5122+ else :
5123+ print (n )
5124+ evaluation .message ("FileNames" , "badn" , n )
5125+ return
5126+ else :
5127+ n = 1
5128+
5129+ # list the files
5130+ if options .get ('System`IgnoreCase' , None ) == SymbolTrue :
5131+ patterns = [re .compile ("^" +
5132+ to_regex (p , evaluation ,
5133+ abbreviated_patterns = True ),
5134+ re .IGNORECASE )+ "$"
5135+ for p in str_forms ]
5136+ else :
5137+ patterns = [re .compile ("^" +
5138+ to_regex (p ,
5139+ evaluation ,
5140+ abbreviated_patterns = True ) +
5141+ "$" ) for p in str_forms ]
5142+
5143+ for path in str_paths :
5144+ if not osp .isdir (path ):
5145+ continue
5146+ if n == 1 :
5147+ for fn in os .listdir (path ):
5148+ fullname = osp .join (path , fn )
5149+ for pattern in patterns :
5150+ if pattern .match (fn ):
5151+ filenames .add (fullname )
5152+ break
5153+ else :
5154+ pathlen = len (path )
5155+ for root , dirs , files in os .walk (path ):
5156+ # FIXME: This is an ugly and inefficient way
5157+ # to avoid looking deeper than the level n, but I do not realize
5158+ # how to do this better without a lot of code...
5159+ if n is not None and len (root [pathlen :].split (osp .sep ))> n :
5160+ continue
5161+ for fn in files + dirs :
5162+ for pattern in patterns :
5163+ if pattern .match (fn ):
5164+ filenames .add (osp .join (root ,fn ))
5165+ break
5166+
5167+
5168+ return Expression ("List" , * [String (s ) for s in filenames ])
0 commit comments