~ubuntu-branches/ubuntu/vivid/frescobaldi/vivid

« back to all changes in this revision

Viewing changes to python/ly/indent.py

  • Committer: Package Import Robot
  • Author(s): Ryan Kavanagh
  • Date: 2012-01-03 16:20:11 UTC
  • mfrom: (1.4.1)
  • Revision ID: package-import@ubuntu.com-20120103162011-tsjkwl4sntwmprea
Tags: 2.0.0-1
* New upstream release 
* Drop the following uneeded patches:
  + 01_checkmodules_no_python-kde4_build-dep.diff
  + 02_no_pyc.diff
  + 04_no_binary_lilypond_upgrades.diff
* Needs new dependency python-poppler-qt4
* Update debian/watch for new download path
* Update copyright file with new holders and years

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# This file is part of the Frescobaldi project, http://www.frescobaldi.org/
2
 
#
3
 
# Copyright (c) 2008, 2009, 2010 by Wilbert Berendsen
4
 
#
5
 
# This program is free software; you can redistribute it and/or
6
 
# modify it under the terms of the GNU General Public License
7
 
# as published by the Free Software Foundation; either version 2
8
 
# of the License, or (at your option) any later version.
9
 
#
10
 
# This program is distributed in the hope that it will be useful,
11
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 
# GNU General Public License for more details.
14
 
#
15
 
# You should have received a copy of the GNU General Public License
16
 
# along with this program; if not, write to the Free Software
17
 
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
 
# See http://www.gnu.org/licenses/ for more information.
19
 
 
20
 
from __future__ import unicode_literals
21
 
 
22
 
"""
23
 
Indent LilyPond input.
24
 
 
25
 
Recognizes common LilyPond mode and Scheme mode.
26
 
 
27
 
This module is not dependent on any other module,
28
 
besides the Python standard re module.
29
 
"""
30
 
 
31
 
import re
32
 
 
33
 
# tokens to look for in LilyPond mode
34
 
lily_re = (
35
 
    r"(?P<indent>\{|<<)"
36
 
    r"|(?P<dedent>>>|\})"
37
 
    r'|(?P<string>"(\\[\\"]|[^"])*")'
38
 
    r"|(?P<newline>\n[^\S\n]*)"
39
 
    r"|(?P<space>[^\S\n]+)"
40
 
    r"|(?P<scheme>#)"
41
 
    r"|(?P<blockcomment>%\{.*?%\})"
42
 
    r"|(?P<longcomment>%%%[^\n]*)"
43
 
    r"|(?P<comment>%[^\n]*)"
44
 
    )
45
 
 
46
 
# tokens to look for in Scheme mode
47
 
scheme_re = (
48
 
    r"(?P<indent>\()"
49
 
    r"|(?P<dedent>\))"
50
 
    r'|(?P<string>"(\\[\\"]|[^"])*")'
51
 
    r"|(?P<newline>\n[^\S\n]*)"
52
 
    r"|(?P<space>[^\S\n]+)"
53
 
    r"|(?P<lilypond>#\{)"
54
 
    r"|(?P<longcomment>;;;[^\n]*)"
55
 
    r"|(?P<blockcomment>#!.*?!#)"
56
 
    r"|(?P<comment>;[^\n]*)"
57
 
    )
58
 
 
59
 
# tokens to look for in LilyPond-inside-Scheme mode
60
 
schemelily_re = r"(?P<backtoscheme>#\})|" + lily_re
61
 
 
62
 
 
63
 
# Parse LilyPond text
64
 
lily = re.compile(lily_re, re.DOTALL)
65
 
 
66
 
# Parse LilyPond-in-Scheme text
67
 
schemelily = re.compile(schemelily_re, re.DOTALL)
68
 
 
69
 
# Parse Scheme text, instantiate to keep state (depth)
70
 
class scheme:
71
 
    search = re.compile(scheme_re, re.DOTALL).search
72
 
    depth = 0
73
 
 
74
 
 
75
 
# searches for indent inside a string
76
 
indent_rx = re.compile(r'\n([^\S\n]*)')
77
 
 
78
 
    
79
 
def indent(text,
80
 
        start = None,
81
 
        indentwidth = 2,
82
 
        tabwidth = 8,
83
 
        usetabs = None,
84
 
        startscheme = False
85
 
        ):
86
 
    """
87
 
    Properly indents the LilyPond input in text.
88
 
    
89
 
    If start is an integer value, use that value as the indentwidth to start
90
 
    with, disregarding the current indent of the first line.
91
 
    If it is None, use the indent of the first line.
92
 
    
93
 
    indentwidth: how many positions to indent (default 2)
94
 
    tabwidth: width of a tab character
95
 
    usetabs: whether to use tab characters in the indent:
96
 
        - None = determine from document
97
 
        - True = use tabs for the parts of the indent that exceed the tab width
98
 
        - False = don't use tabs.
99
 
    startscheme: start in scheme mode (not very robust)
100
 
    """
101
 
    
102
 
    # record length of indent of first line
103
 
    space = re.match(r'[^\S\n]*', text).group()
104
 
    if start is None:
105
 
        start = len(space.expandtabs(tabwidth))
106
 
    if usetabs is None:
107
 
        usetabs = '\t' in space or '\n\t' in text
108
 
    
109
 
    mode = [lily]       # the mode to parse in
110
 
    indent = [start]    # stack with indent history
111
 
    pos = len(space)    # start position in text
112
 
    output = []         # list of output lines
113
 
    
114
 
    if startscheme:
115
 
        mode.append(scheme())
116
 
    if usetabs:
117
 
        makeindent = lambda i: '\t' * int(i / tabwidth) + ' ' * (i % tabwidth)
118
 
    else:
119
 
        makeindent = lambda i: ' ' * i
120
 
    
121
 
    line = []           # list to build the output, per line
122
 
    curindent = -1      # current indent in count of spaces, -1 : not yet set
123
 
    
124
 
    # Search the text from the previous position
125
 
    # (very fast: does not alter the string in text)
126
 
    m = mode[-1].search(text, pos)
127
 
    while m:
128
 
        # also append text before the found token
129
 
        more = pos < m.start()
130
 
        if more:
131
 
            line.append(text[pos:m.start()])
132
 
        
133
 
        # type, text, and new position for next search
134
 
        item, token, pos = m.lastgroup, m.group(), m.end()
135
 
 
136
 
        # If indent not yet determined, set it to 0 if we found a long comment
137
 
        # (with three or more %%% or ;;; characters). Was any other text found,
138
 
        # keep the current indent level for the current line.
139
 
        # (Our current indent can change if our line starts with dedent tokens.)
140
 
        if curindent == -1:
141
 
            if item == 'longcomment':
142
 
                curindent = 0
143
 
            elif (more or item not in ('dedent', 'space', 'backtoscheme')):
144
 
                curindent = indent[-1]
145
 
        
146
 
        # Check if we found a multiline block comment.
147
 
        # Thoses are handled specially. Indents inside the block comment are
148
 
        # preserved but positioned as close as possible to the current indent.
149
 
        # So the algorithm cuts the shortest indent off from all lines and then
150
 
        # adds the current indent.
151
 
        if item == 'blockcomment' and '\n' in token:
152
 
            # Find the shortest indent inside the block comment
153
 
            shortest = min(len(n.group(1).expandtabs(tabwidth))
154
 
                for n in indent_rx.finditer(token))
155
 
            # Remove that indent from all lines
156
 
            fixindent = lambda n: '\n' + makeindent(
157
 
                curindent - shortest + len(n.group(1).expandtabs(tabwidth)))
158
 
            token = indent_rx.sub(fixindent, token)
159
 
        
160
 
        elif mode[-1] in (lily, schemelily):
161
 
            # we are parsing in LilyPond mode.
162
 
            if item == 'indent':
163
 
                indent.append(indent[-1] + indentwidth)
164
 
            elif item == 'dedent' and len(indent) > 1:
165
 
                indent.pop()
166
 
            elif item == 'scheme':
167
 
                mode.append(scheme())       # enter scheme mode
168
 
            elif item == 'backtoscheme':
169
 
                indent.pop()
170
 
                mode.pop()                  # leave lilypond mode, back to scheme
171
 
        else:
172
 
            # we are parsing in Scheme mode.
173
 
            if item == 'indent':
174
 
                mode[-1].depth += 1         # count parentheses
175
 
                # look max 10 characters ahead to vertically align opening
176
 
                # parentheses, but stop at closing parenthesis, quote or newline.
177
 
                n = re.search(r'[()"\n]', text[pos:pos+10])
178
 
                if n and n.group() == '(':
179
 
                    indent.append(indent[-1] + n.start() + 1)
180
 
                else:
181
 
                    indent.append(indent[-1] + indentwidth)
182
 
                
183
 
            elif item == 'dedent':
184
 
                if mode[-1].depth:
185
 
                    indent.pop()
186
 
                if mode[-1].depth <= 1:
187
 
                    mode.pop()              # leave scheme mode
188
 
                else:
189
 
                    mode[-1].depth -= 1     # count parentheses backwards
190
 
            elif item == 'lilypond':
191
 
                mode.append(schemelily)     # enter lilypond-in-scheme mode
192
 
                indent.append(indent[-1] + indentwidth)
193
 
            elif mode[-1].depth == 0:
194
 
                # jump out if we got one atom or are at a space or end of line
195
 
                # and still no opening parenthesis. But stay if we only just
196
 
                # had a hash(#).
197
 
                if (item in ('string', 'comment', 'longcomment')
198
 
                    or (more and item in ('newline', 'space'))):
199
 
                    mode.pop()
200
 
        
201
 
        if item == 'newline':
202
 
            # Write out the line
203
 
            output.append(makeindent(curindent) + ''.join(line))
204
 
            line = []
205
 
            curindent = -1
206
 
        else:
207
 
            line.append(token)
208
 
        
209
 
        # On to the next token
210
 
        m = mode[-1].search(text, pos)
211
 
    
212
 
    # Still some text left?
213
 
    if pos < len(text):
214
 
        line.append(text[pos:])
215
 
    if line:
216
 
        if curindent == -1:
217
 
            curindent = indent[-1]
218
 
        output.append(makeindent(curindent) + ''.join(line))
219
 
    else:
220
 
        output.append(makeindent(start))
221
 
    # Return formatted output
222
 
    return '\n'.join(output)
223
 
 
224
 
    
225
 
if __name__ == '__main__':
226
 
    
227
 
    import sys, optparse
228
 
    
229
 
    op = optparse.OptionParser(usage='usage: %prog [options] [filename]')
230
 
    op.add_option('-o', '--output',
231
 
        help='write to this file instead of standard output')
232
 
    op.add_option('-i', '--indent-width', type='int', default=2,
233
 
        help='indent width in characters to use [default: %default]')
234
 
    op.add_option('-t', '--tab-width', type='int', default=8,
235
 
        help='tab width to assume [default: %default]')
236
 
    op.add_option('-s', '--start-indent', type='int', default=0,
237
 
        help='start indent [default: %default]')
238
 
    op.add_option('--scheme', action='store_true',
239
 
        help='start indenting in Scheme mode')
240
 
    op.add_option('-u', '--use-tabs', action='store_true',
241
 
        help='use tabs instead of spaces for indent')
242
 
    options, args = op.parse_args()
243
 
    # TODO: encoding
244
 
    # TODO: error handling
245
 
    infile = args and open(args[0]) or sys.stdin
246
 
    text = infile.read()
247
 
    text = indent(text,
248
 
        start=options.start_indent,
249
 
        indentwidth=options.indent_width,
250
 
        tabwidth=options.tab_width,
251
 
        usetabs=options.use_tabs,
252
 
        startscheme=options.scheme
253
 
        )
254
 
    outfile = options.output and (options.output) or sys.stdout
255
 
    outfile.write(text)
256
 
    
257
 
    if infile is not sys.stdin:
258
 
        infile.close()
259
 
    if outfile is not sys.stdout:    
260
 
        outfile.close()
261
 
    sys.exit(0)