File: fileinfo.py

package info (click to toggle)
frescobaldi 3.0.0~git20161001.0.eec60717%2Bds1-2
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 19,792 kB
  • ctags: 5,843
  • sloc: python: 37,853; sh: 180; makefile: 69
file content (194 lines) | stat: -rw-r--r-- 6,388 bytes parent folder | download | duplicates (2)
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
# This file is part of the Frescobaldi project, http://www.frescobaldi.org/
#
# Copyright (c) 2008 - 2014 by Wilbert Berendsen
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
# See http://www.gnu.org/licenses/ for more information.

"""
Computes and caches various information about files.
"""


import itertools
import re
import os
import atexit

import ly.document
import lydocinfo
import ly.lex
import filecache
import util
import variables


_document_cache = filecache.FileCache()
_suffix_chars_re = re.compile(r'[^-\w]', re.UNICODE)


### XXX otherwise I get a segfault on shutdown when very large music trees
### are made (and every node references the document).
### (The segfault is preceded by a "corrupted double-linked list" message.)
atexit.register(_document_cache.clear)


class _CachedDocument(object):
    """Contains a document and related items."""
    filename = None
    document = None
    variables = None
    docinfo = None
    music = None


def _cached(filename):
    """Return a _CachedDocument instance for the filename, else creates one."""
    filename = os.path.realpath(filename)
    try:
        c = _document_cache[filename]
    except KeyError:
        with open(filename, 'rb') as f:
            text = util.decode(f.read())
        c = _document_cache[filename] = _CachedDocument()
        c.variables = v = variables.variables(text)
        c.document = ly.document.Document(text, v.get("mode"))
        c.filename = c.document.filename = filename
    return c


def document(filename):
    """Return a (cached) ly.document.Document for the filename."""
    return _cached(filename).document


def docinfo(filename):
    """Return a (cached) LyDocInfo instance for the specified file."""
    c = _cached(filename)
    if c.docinfo is None:
        c.docinfo = lydocinfo.DocInfo(c.document, c.variables)
    return c.docinfo


def music(filename):
    """Return a (cached) music.Document instance for the specified file."""
    c = _cached(filename)
    if c.music is None:
        import music
        c.music = music.Document(c.document)
    return c.music
    

def textmode(text, guess=True):
    """Returns the type of the given text ('lilypond, 'html', etc.).
    
    Checks the mode variable and guesses otherwise if guess is True.
    
    """
    mode = variables.variables(text).get("mode")
    if mode in ly.lex.modes:
        return mode
    if guess:
        return ly.lex.guessMode(text)


def includefiles(dinfo, include_path=()):
    """Returns a set of filenames that are included by the DocInfo's document.
        
    The specified include path is used to find files. The own filename 
    is NOT added to the set. Included files are checked recursively, 
    relative to our file, relative to the including file, and if that 
    still yields no file, relative to the directories in the include_path.
    
    If the document has no local filename, only the include_path is 
    searched for files.
    
    """
    filename = dinfo.document.filename
    basedir = os.path.dirname(filename) if filename else None
    files = set()
    
    def tryarg(directory, arg):
        path = os.path.realpath(os.path.join(directory, arg))
        if path not in files and os.path.isfile(path):
            files.add(path)
            args = docinfo(path).include_args()
            find(args, os.path.dirname(path))
            return True
            
    def find(incl_args, directory):
        for arg in incl_args:
            # new, recursive, relative include
            if not (directory and tryarg(directory, arg)):
                # old include (relative to master file)
                if not (basedir and tryarg(basedir, arg)):
                    # if path is given, also search there:
                    for p in include_path:
                        if tryarg(p, arg):
                            break
    
    find(dinfo.include_args(), basedir)
    return files


def basenames(dinfo, includefiles=(), filename=None, replace_suffix=True):
    """Returns the list of basenames a document is expected to create.
    
    The list is created based on includefiles and the define output-suffix and
    \bookOutputName and \bookOutputSuffix commands.
    You should add '.ext' and/or '-[0-9]+.ext' to find created files.
    
    If filename is given, it is regarded as the filename LilyPond is run on. 
    Otherwise, the filename of the info's document is read.
    
    If replace_suffix is True (the default), special characters and spaces 
    in the suffix are replaced with underscores (in the same way as LilyPond 
    does it), using the replace_suffix_chars() function.
    
    """
    basenames = []
    basepath = os.path.splitext(filename or dinfo.document.filename)[0]
    dirname, basename = os.path.split(basepath)

    if basepath:
        basenames.append(basepath)
    
    def args():
        yield dinfo.output_args()
        for filename in includefiles:
            yield docinfo(filename).output_args()
                
    for type, arg in itertools.chain.from_iterable(args()):
        if type == "suffix":
            if replace_suffix:
                # LilyPond (lily-library.scm:223) does this, too
                arg = replace_suffix_chars(arg)
            arg = basename + '-' + arg
        path = os.path.normpath(os.path.join(dirname, arg))
        if path not in basenames:
            basenames.append(path)
    return basenames


def replace_suffix_chars(s):
    """Replace spaces and most non-alphanumeric characters with underscores.
    
    This is used to mimic the behaviour of LilyPond, which also does this,
    for the output-suffix. (See scm/lily-library.scm:223.)
    
    """
    return _suffix_chars_re.sub('_', s)