File: page.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 (277 lines) | stat: -rw-r--r-- 9,604 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
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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# This file is part of the Frescobaldi project, http://www.frescobaldi.org/
#
# Copyright (c) 2013 - 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.

"""
Page, a page from the Frescobaldi User Manual.
"""


import re

from PyQt5.QtCore import QSettings
from PyQt5.QtGui import QKeySequence

import simplemarkdown

from . import read
from . import resolve


class Page(object):
    def __init__(self, name=None):
        self._attrs = {}
        self._title = None
        self._body = None
        self._name = None
        if name:
            self.load(name)
    
    def load(self, name):
        """Parse and translate the named document."""
        self._name = name
        try:
            doc, attrs = read.document(name)
        except (OSError, IOError):
            doc, attrs = read.document('404')
        attrs.setdefault('VARS', []).append('userguide_page md `{0}`'.format(name))
        self.parse_text(doc, attrs)
        
    def parse_text(self, text, attrs=None):
        """Parse and translate the document."""
        self._attrs = attrs or {}
        t = self._tree = simplemarkdown.Tree()
        read.Parser().parse(text, t)
    
    def is_popup(self):
        """Return True if the helppage should be displayed as a popup."""
        try:
            return 'popup' in self._attrs['PROPERTIES']
        except KeyError:
            return False
    
    def title(self):
        """Return the title"""
        if self._title is None:
            self._title = "No Title"
            for heading in self._tree.find('heading'):
                self._title = self._tree.text(heading)
                break
        return self._title
    
    def body(self):
        """Return the HTML body."""
        if self._body is None:
            output = HtmlOutput()
            output.resolver = Resolver(self._attrs.get('VARS'))
            self._tree.copy(output)
            html = output.html()
            # remove empty paragraphs (could result from optional text)
            html = html.replace('<p></p>', '')
            self._body = html
        return self._body
        
    def children(self):
        """Return the list of names of child documents."""
        return self._attrs.get("SUBDOCS") or []
    
    def seealso(self):
        """Return the list of names of "see also" documents."""
        return self._attrs.get("SEEALSO") or []
    

class HtmlOutput(simplemarkdown.HtmlOutput):
    """Colorizes LilyPond source and replaces {variables}.
    
    Put a Resolver instance in the resolver attribute before populating
    the output.
    
    """
    heading_offset = 1
    
    def code_start(self, code, specifier=None):
        if specifier == "lilypond":
            import highlight2html
            self._html.append(highlight2html.html_text(code, full_html=False))
        else:
            self.tag('code')
            self.tag('pre')
            self.text(code)
    
    def code_end(self, code, specifier=None):
        if specifier != "lilypond":
            self.tag('/pre')
            self.tag('/code')
        self.nl()
    
    def inline_text_start(self, text):
        text = self.html_escape(text)
        text = self.resolver.format(text)   # replace {variables} ...
        self._html.append(text)


class Resolver(object):
    """Resolves variables in help documents."""
    def __init__(self, variables=None):
        """Initialize with a list of variables from the #VARS section.
        
        Every item is simply a line, where the first word is the name,
        the second the type and the rest is the contents.
        
        """
        self._variables = d = {}
        if variables:
            for v in variables:
                try:
                    name, type, text = v.split(None, 2)
                except ValueError:
                    continue
                d[name] = (type, text)
    
    def format(self, text):
        """Replaces all {variable} items in the text."""
        return read._variable_re.sub(self.replace, text)
        
    def replace(self, matchObj):
        """Return the replace string for the match.
        
        For a match like {blabla}, self.resolve('blabla') is called, and if
        the result is not None, '{blabla}' is replaced with the result.
        
        """
        result = self.resolve(matchObj.group(1))
        return matchObj.group() if result is None else result
    
    def resolve(self, name):
        """Try to find the value for the named variable.
        
        First, the #VARS section is searched. If that yields no result,
        the named function in the resolve module is called. If that yields
        no result either, None is returned.
        
        """
        
        try:
            typ, text = self._variables[name]
        except KeyError:
            try:
                return getattr(resolve, name)()
            except AttributeError:
                return
        try:
            method = getattr(self, 'handle_' + typ.lower())
        except AttributeError:
            method = self.handle_text
        return method(text)

    def handle_md(self, text):
        """Convert inline markdown to HTML."""
        return simplemarkdown.html_inline(text)

    def handle_html(self, text):
        """Return text as is, it may contain HTML."""
        return text
    
    def handle_text(self, text):
        """Return text escaped, it will not be represented as HTML."""
        return simplemarkdown.html_escape(text)

    def handle_url(self, text):
        """Return a clickable url."""
        url = text
        if text.startswith('http://'):
            text = text[7:]
        if text.endswith('/'):
            text = text[:-1]
        url = simplemarkdown.html_escape(url).replace('"', '&quot;')
        text = simplemarkdown.html_escape(text)
        return '<a href="{0}">{1}</a>'.format(url, text)

    def handle_help(self, text):
        """Return a link to the specified help page, with the title."""
        title = Page(text).title()
        url = text
        return '<a href="{0}">{1}</a>'.format(url, title)

    def handle_shortcut(self, text):
        """Return the keystroke currently defined for the action."""
        collection_name, action_name = text.split(None, 1)
        import actioncollectionmanager
        action = actioncollectionmanager.action(collection_name, action_name)
        seq = action.shortcut()
        key = seq.toString(QKeySequence.NativeText) or _("(no key defined)")
        return '<span class="shortcut">{0}</span>'.format(simplemarkdown.html_escape(key))
    
    def handle_menu(self, text):
        """Split the text on '->' in menu or action titles and translate them.
        
        The pieces are then formatted as a nice menu path.
        When an item contains a "|", the part before the "|" is the message
        context.
        
        When an item starts with "!", the accelerators are not removed (i.e.
        it is not an action or menu name).
        
        """
        pieces = [name.strip() for name in text.split('->')]
        import qutil
        def title(name):
            """Return a translated title for the name."""
            try:
                name = {
                    # untranslated standard menu names
                    'file': 'menu title|&File',
                    'edit': 'menu title|&Edit',
                    'view': 'menu title|&View',
                    'snippets': 'menu title|Sn&ippets',
                    'music': 'menu title|&Music',
                    'lilypond': 'menu title|&LilyPond',
                    'tools': 'menu title|&Tools',
                    'window': 'menu title|&Window',
                    'session': 'menu title|&Session',
                    'help': 'menu title|&Help',
                }[name]
            except KeyError:
                pass
            if name.startswith('!'):
                removeAccel = False
                name = name[1:]
            else:
                removeAccel = True
            try:
                ctxt, msg = name.split('|', 1)
                translation = _(ctxt, msg)
            except ValueError:
                translation = _(name)
            if removeAccel:
                translation = qutil.removeAccelerator(translation).strip('.')
            return translation
            
        translated = [title(name) for name in pieces]
        return '<em>{0}</em>'.format(' &#8594; '.join(translated))
        
    def handle_image(self, filename):
        url = simplemarkdown.html_escape(filename).replace('"', '&quot;')
        return '<img src="{0}" alt="{0}"/>'.format(url)

    def handle_languagename(self, code):
        """Return a language name in the current language."""
        import po.setup
        import language_names
        return language_names.languageName(code, po.setup.current())