Skip to content

Commit ebe0ba4

Browse files
authored
Merge pull request #1173 from mathics/filenames-implement
Filenames implement
2 parents 4be33d2 + f52ac9c commit ebe0ba4

8 files changed

Lines changed: 430 additions & 38 deletions

File tree

CHANGES.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
CHANGES
22
=======
33

4+
New builtins
5+
++++++++++++
6+
ByteArray
7+
FileNames
8+
CreateFile
9+
CreateTemporary
10+
11+
12+
413
2.0.0
514
-----
615

mathics/autoload/formats/CSV/Export.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
FunctionChannels -> {"Streams"},
2323
Options -> {"ByteOrderMark"},
2424
DefaultElement -> "Plaintext",
25-
BinaryFormat -> True,
25+
BinaryFormat -> False,
2626
Options -> {
2727
"CharacterEncoding",
2828
"FieldSeparators"

mathics/builtin/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
MachineReal,
2222
PrecisionReal,
2323
String,
24+
ByteArrayAtom,
2425
Symbol,
2526
ensure_context,
2627
strip_context,

mathics/builtin/files.py

Lines changed: 245 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import requests
2020
import pathlib
2121

22+
from io import BytesIO, StringIO
2223
import os.path as osp
2324
from itertools import chain
2425

@@ -37,6 +38,7 @@
3738
SymbolFalse,
3839
SymbolNull,
3940
SymbolTrue,
41+
SymbolInfinity,
4042
from_python,
4143
Integer,
4244
BoxError,
@@ -47,10 +49,10 @@
4749
from mathics.core.numbers import dps
4850
from mathics.builtin.base import Builtin, Predefined, BinaryOperator, PrefixOperator
4951
from 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
5153
from mathics.builtin.base import MessageException
5254
from mathics.settings import ROOT_DIR
53-
55+
import re
5456

5557
INITIAL_DIR = os.getcwd()
5658
HOME_DIR = osp.expanduser("~")
@@ -62,6 +64,34 @@
6264
PATH_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+
6595
def 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])

mathics/builtin/graphics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3224,7 +3224,7 @@ def boxes_to_xml(self, leaves=None, **options):
32243224
)
32253225

32263226
return (
3227-
'<mglyph width="%dpx" height="%dpx" src="data:image/svg+xml;base64,%s"/>'
3227+
'<mtext><img width="%dpx" height="%dpx" src="data:image/svg+xml;base64,%s"/></mtext>'
32283228
% (
32293229
int(width),
32303230
int(height),

0 commit comments

Comments
 (0)