File: version.py

package info (click to toggle)
frescobaldi 1.1.4-1
  • links: PTS, VCS
  • area: main
  • in suites: squeeze
  • size: 4,240 kB
  • ctags: 2,434
  • sloc: python: 15,614; lisp: 28; sh: 25; makefile: 2
file content (232 lines) | stat: -rw-r--r-- 7,528 bytes parent folder | download
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
# This file is part of the Frescobaldi project, http://www.frescobaldi.org/
#
# Copyright (c) 2008, 2009, 2010 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.

from __future__ import unicode_literals

"""
LilyPond version information
"""

import os, re, weakref
from functools import wraps
from subprocess import Popen, PIPE, STDOUT

import ly.rx

def getVersion(text):
    """
    Determine the version of a LilyPond document.
    Returns a Version instance or None.
    """
    text = ly.rx.all_comments.sub('', text)
    match = re.search(r'\\version\s*"(.*?)"', text)
    if match:
        return Version.fromString(match.group(1))


# Utility functions.....
def cacheresult(func):
    """
    A decorator that performs the decorated method call only the first time,
    caches the return value, and returns that next time.
    The argments tuple should be hashable.
    """
    cache = weakref.WeakKeyDictionary()
    @wraps(func)
    def wrapper(obj, *args):
        h = hash(args)
        try:
            return cache[obj][h]
        except KeyError:
            result = cache.setdefault(obj, {})[h] = func(obj, *args)
            return result
    return wrapper


class Version(tuple):
    """
    Contains a version as a two- or three-tuple (major, minor [, patchlevel]).
    
    Can format itself as "major.minor" or "major.minor.patch"
    Additionally, three attributes are defined:
    - major     : contains the major version number as an int
    - minor     : contains the minor version number as an int
    - patch     : contains the patch level as an int or None
    """
    def __new__(cls, major, minor, patch=None):
        if patch is None:
            obj = tuple.__new__(cls, (major, minor))
        else:
            obj = tuple.__new__(cls, (major, minor, patch))
        obj.major = major
        obj.minor = minor
        obj.patch = patch
        return obj
        
    def __format__(self, formatString):
        return str(self)
        
    def __str__(self):
        return ".".join(map(str, self))

    @classmethod
    def fromString(cls, text):
        match = re.search(r"(\d+)\.(\d+)(?:\.(\d+))?", text)
        if match:
            return cls(*map(lambda g: int(g) if g else None, match.groups()))

            
class LilyPondInstance(object):
    """
    Contains information about a LilyPond instance, referred to by a command
    string defaulting to 'lilypond'.
    """
    
    # name of the convert-ly command
    convert_ly_name = 'convert-ly'
    
    def __new__(cls, command='lilypond', cache=True):
        """
        Return a cached instance if available and cache == True.
        """
        if '_cache' not in cls.__dict__:
            cls._cache = {}
        elif cache and command in cls._cache:
            return cls._cache[command]
        obj = cls._cache[command] = object.__new__(cls)
        obj._command = command
        return obj
    
    @cacheresult
    def command(self):
        """
        Returns the command with full path prepended.
        """
        cmd = self._command
        if os.path.isabs(cmd):
            return cmd
        elif os.path.isabs(os.path.expanduser(cmd)):
            return os.path.expanduser(cmd)
        elif os.sep in cmd and os.access(cmd, os.X_OK):
            return os.path.abspath(cmd)
        else:
            for p in os.environ.get("PATH", os.defpath).split(os.pathsep):
                if os.access(os.path.join(p, cmd), os.X_OK):
                    return os.path.join(p, cmd)
    
    def bindir(self):
        """
        Returns the directory the LilyPond command is in.
        """
        cmd = self.command()
        if cmd:
            return os.path.dirname(cmd)
    
    def path_to(self, command):
        """
        Returns the full path to the given command, by joining our bindir() with
        the command.
        """
        bindir = self.bindir()
        if bindir:
            return os.path.join(bindir, command)
            
    def convert_ly(self):
        """
        DEPRECATED: Use path_to('convert-ly') instead.
        Returns the full path of the convert-ly command that is in the
        same directory as the corresponding lilypond command.
        """
        return self.path_to(self.convert_ly_name)
            
    def prefix(self):
        """
        Returns the prefix of a command. E.g. if command is "lilypond"
        and resolves to "/usr/bin/lilypond", this method returns "/usr".
        """
        cmd = self.command()
        if cmd:
            return os.path.dirname(os.path.dirname(cmd))
        
    @cacheresult
    def version(self):
        """
        Returns the version returned by command -v as an instance of Version.
        """
        try:
            output = Popen((self._command, '-v'), stdout=PIPE, stderr=STDOUT).communicate()[0]
            return Version.fromString(output)
        except OSError:
            pass

    @cacheresult
    def datadir(self):
        """
        Returns the datadir of this LilyPond instance. Most times something
        like "/usr/share/lilypond/2.13.3/"
        """
        # First ask LilyPond itself.
        try:
            d = Popen((self._command, '-e',
                "(display (ly:get-option 'datadir)) (newline) (exit)"),
                stdout=PIPE).communicate()[0].strip()
            if os.path.isabs(d) and os.path.isdir(d):
                return d
        except OSError:
            pass
        # Then find out via the prefix.
        version, prefix = self.version(), self.prefix()
        if prefix:
            dirs = ['current']
            if version:
                dirs.append(str(version))
            for suffix in dirs:
                d = os.path.join(prefix, 'share', 'lilypond', suffix)
                if os.path.isdir(d):
                    return d

    @cacheresult
    def lastConvertLyRuleVersion(self):
        """
        Returns the version of the last convert-ly rule of this lilypond
        instance.
        """
        try:
            output = Popen((self.convert_ly(), '--show-rules'), stdout=PIPE).communicate()[0]
            for line in reversed(output.splitlines()):
                match = re.match(r"(\d+)\.(\d+)\.(\d+):", line)
                if match:
                    return Version(*map(int, match.groups()))
        except OSError:
            pass
        
    @cacheresult
    def fontInfo(self, fontname):
        """
        Returns a SvgFontInfo object containing information about
        the named font (e.g. "emmentaler-20").
        """
        datadir = self.datadir()
        if datadir:
            font = os.path.join(datadir, "fonts", "svg", fontname + ".svg")
            if os.path.exists(font):
                import ly.font
                return ly.font.SvgFontInfo(font)