This file is indexed.

/usr/share/sagemath/bin/sage-preparse is in sagemath-common 7.4-9.

This file is owned by root:root, with mode 0o755.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
#!/usr/bin/env python
"""
Preparse .sage files and save the result to .sage.py files.

AUTHOR:
    -- William Stein (2005): first version
    -- William Stein (2008): fix trac #2391 and document the code.
    -- Dan Drake (2009): fix trac #5052
    -- Dan Drake (2010-12-08): fix trac #10440
    -- Johan S. R. Nielsen (2015-11-06): fix trac #17019
"""

import os, sys, re

from sage.repl.preparse import preparse_file
from sage.misc.temporary_file import atomic_write

# The spkg/bin/sage script passes the files to be preparsed as
# arguments (but remove sys.argv[0]).
files = sys.argv[1:]

# There must be at least 1 file or we display an error/usage message
# and exit
if len(files) == 0:
    print("""Usage: {} <file1.sage> <file2.sage>...
Creates files file1.sage.py, file2.sage.py ... that are the Sage
preparsed versions of file1.sage, file2.sage ...
If a non-autogenerated .sage.py file with the same name exists, you will
receive an error and the file will not be overwritten.""".format(sys.argv[0]))
    sys.exit(1)

# The module-scope variable contains a list of all files we
# have seen while preparsing a given file.  The point of this
# is that we want to avoid preparsing a file we have already
# seen, since then infinite loops would result from mutual
# recursive includes.
files_so_far = []

# This message is inserted in autogenerated files so that the reader
# will know, and so we know it is safe to overwrite them.
AUTOGEN_MSG = "# This file was *autogenerated* from the file "

# We use this regexp to parse lines with load or attach statements.
# Here's what it looks for:
#
# A (possibly empty) sequence of whitespace at the beginning of the
# line, saved as a group named 'lws';
#
#   followed by
#
# the word "load" or "attach";
#
#   followed by
#
# a nonempty sequence of whitespace;
#
#   followed by
#
# whatever else is on the line, saved as a group named 'files'.
#
# We want to save the leading white space so that we can maintain
# correct indentation in the preparsed file.
load_or_attach = re.compile(r"^(?P<lws>\s*)(load|attach)\s+(?P<files>.*)$")

def do_preparse(f, files_before=[]):
    """
    Preparse the file f and write the result out to a filename
    with extension .sage.py.

    INPUT:

    - ``f`` -- string: the name of a file
    - ``files_before`` -- list of strings of previous filenames loaded (to avoid circular loops)

    OUTPUT: None (writes a file with extension .sage.py to disk).
    """
    if f in files_so_far:
        return
    files_so_far.append(f)
    if not os.path.exists(f):
        print("{}: File '{}' is missing".format(sys.argv[0], f))
        return
    if f.endswith('.py'):
        return
    if not f.endswith('.sage'):
        print("{}: Unknown file type {}".format(sys.argv[0], f))
        sys.exit(1)

    fname = f + ".py"
    if os.path.exists(fname):
        if AUTOGEN_MSG not in open(fname).read():
            print("Refusing to overwrite existing non-autogenerated file {!r}."
                  .format(os.path.abspath(fname)))
            print("Please delete or move this file manually.")
            sys.exit(1)

    # TODO:
    # I am commenting this "intelligence" out, since, e.g., if I change
    # the preparser between versions this can cause problems.  This
    # is an optimization that definitely isn't needed at present, since
    # preparsing is so fast.
    # Idea: I could introduce version numbers, though....
    #if os.path.exists(fname) and os.path.getmtime(fname) >= os.path.getmtime(f):
    #    return

    # Finally open the file
    F = open(f).read()

    # Check to see if a coding is specified in the .sage file. If it is,
    # then we want to copy it over to the new file and not include it in
    # the preprocessing. If both the first and second line have an
    # encoding declaration, the second line's encoding will get used.

    lines = F.splitlines()
    coding = ''
    for num, line in enumerate(lines[:2]):
        if re.search(r"coding[:=]\s*([-\w.]+)", line):
            coding = line + '\n'
            F = '\n'.join(lines[:num] + lines[(num+1):])

    # It is ** critical ** that all the preparser-stuff we put into
    # the file are put after the module docstring, since
    # otherwise the docstring will not be understood by Python.
    i = find_position_right_after_module_docstring(F)
    header, body = F[:i] , F[i:]
    # Preparse the body
    body = preparse_file(body)

    # Check for load/attach commands.
    body = do_load_and_attach(body, f, files_before)

    # The Sage library include line along with a autogen message
    sage_incl = '%s%s\nfrom sage.all_cmdline import *   # import sage library\n'%(AUTOGEN_MSG, f)

    # Finally, write out the result.  We use atomic_write to avoid
    # race conditions (for example, the file will never be half written).
    with atomic_write(fname) as f:
        f.write(coding)
        f.write(header)
        f.write('\n')
        f.write(sage_incl)
        f.write('\n')
        f.write(body)
        f.write('\n')

def find_position_right_after_module_docstring(G):
    """
    Return first position right after the module docstring of G, if it
    has one.  Otherwise return 0.

    INPUT:
        G -- a string
    OUTPUT:
        an integer -- the index into G so that G[i] is right after
                      the module docstring of G, if G has one.
    """
    # The basic idea below is that we look at each line first ignoring
    # all empty lines and commented out lines.  Then we check to see
    # if the next line is a docstring.  If so, we find where that
    # docstring ends.
    v = G.splitlines()
    i = 0
    while i < len(v):
        s = v[i].strip()
        if len(s) > 0 and s[0] != '#':
            break
        i += 1
    if i >= len(v):
        # No module docstring --- entire file is commented out
        return 0

    # Now v[i] contains the first line of the first statement in the file.
    # Is it a docstring?
    n = v[i].lstrip()
    if not (n[0] in ['"',"'"] or n[0:2] in ['r"',"r'"]):
        # not a docstring
        return 0

    # First line is the module docstring.  Where does it end?
    def pos_after_line(k):
        return sum(len(v[j])+1 for j in range(k+1))

    n = n.lstrip('r')  # strip leading r if there is one
    if n[:3] in ["'''", '"""']:
        quotes = n[:3]
        # possibly multiline
        if quotes in n[3:]:
            return pos_after_line(i)
        j = i+1
        while j < len(v) and quotes not in v[j]:
            j += 1
        return pos_after_line(j)
    else:
        # it must be a single line; so add up the lengths of all lines
        # including this one and return that
        return pos_after_line(i)



def do_load_and_attach(G, file, files_before):
    """
    Parse a file G and replace load and attach statements with the
    corresponding execfile() statements.

    INPUT:
        G -- a string; a file loaded in from disk
        file -- the name of the file that contains the non-preparsed
                version of G.
        files_before -- list of files seen so far (don't recurse into
                infinite loop)

    OUTPUT:
        string -- result of parsing load/attach statements in G, i.e.
                  modified version of G with execfiles.
    """
    s = ''
    for t in G.split('\n'):
        z = load_or_attach.match(t)
        if z:
            files = z.group('files').split()
            lws = z.group('lws')
            for w in files:
                name = w.replace(',','').replace('"','').replace("'","")
                #print "'%s'"%name, files_before
                if name in files_before:
                   print("WARNING: not loading {} (in {}) again since would cause circular loop"
                        .format(name, file))
                   continue
                if name.endswith('.sage'):
                    do_preparse(name, files_before + [file])
                    s += lws + "exec(compile(open({0}.py).read(), {0}.py, \
                            'exec'))\n".format(name)
                elif name.endswith('.py'):
                    s += lws + "exec(compile(open({0}).read(), {0}, \
                            'exec'))\n".format(name)
        else:
            s += t + '\n'
    return s


# Here we do the actual work.  We iterate over ever
# file in the input args and create the corresponding
# output file.
for f in files:
    do_preparse(f)