]> git.donarmstrong.com Git - lilypond.git/blob - scripts/auxiliar/makelsr.py
Get texidoc translations out of snippets source files
[lilypond.git] / scripts / auxiliar / makelsr.py
1 #!/usr/bin/env python
2
3 import sys
4 import os
5 import glob
6 import re
7 import optparse
8 import tempfile
9
10 lilypond_flags = "-dno-print-pages -dsafe"
11
12 lys_from_lsr = os.path.join ('Documentation', 'snippets')
13 new_lys = os.path.join ('Documentation', 'snippets', 'new')
14 ly_output = os.path.join (tempfile.gettempdir (), 'lsrtest')
15
16 # which convert-ly to use
17 if os.path.isfile ("out/bin/convert-ly"):
18     conv_path = "out/bin/"
19 elif os.path.isfile ("build/out/bin/convert-ly"):
20     conv_path = "build/out/bin/"
21 else:
22     conv_path=''
23 convert_ly = conv_path + 'convert-ly'
24 lilypond_bin = conv_path + 'lilypond'
25
26
27
28 LY_HEADER_LSR = '''%% DO NOT EDIT this file manually; it is automatically
29 %% generated from LSR http://lsr.dsi.unimi.it
30 %% Make any changes in LSR itself, or in Documentation/snippets/new/ ,
31 %% and then run scripts/auxiliar/makelsr.py
32 %%
33 %% This file is in the public domain.
34 '''
35
36 LY_HEADER_NEW = '''%% DO NOT EDIT this file manually; it is automatically
37 %% generated from %s
38 %% Make any changes in Documentation/snippets/new/
39 %% and then run scripts/auxiliar/makelsr.py
40 %%
41 %% This file is in the public domain.
42 ''' % new_lys
43
44 options_parser = optparse.OptionParser (
45     description = "makelsr - update snippets directory from LSR",
46     usage = '''%%prog [options] [LSR_SNIPPETS_DIR]
47 Unless -s option is specified, this script must be run from top of the
48 source tree. If LSR_SNIPPETS_DIR is not specified, it defaults to
49 current directory.
50
51 Remove snippets in TOP_SOURCE_DIR/%(lys_from_lsr)s and put in snippets
52 from LSR_SNIPPETS_DIR run through convert-ly or from
53 TOP_SOURCE_DIR/%(new_lys)s; if a snippet is present in both
54 directories, the one from TOP_SOURCE_DIR/%(new_lys)s is preferred.
55 All written snippets are copied in LY_OUTPUT
56 with appending translations from .texidoc files and are tested with
57 lilypond with flags %(lilypond_flags)s
58
59 ''' % vars ())
60
61 options_parser.add_option ('-s', '--top-source',
62                            dest="top_source_dir",
63                            action="store",
64                            metavar="TOP_SOURCE_DIR",
65                            default=".",
66                            help="set LilyPond top source directory")
67
68 options_parser.add_option ('-o', '--ly-output',
69                            dest="ly_output",
70                            action="store",
71                            metavar="LY_OUTPUT",
72                            default=ly_output,
73                            help="set LilyPond output files temporary directory")
74
75 options_parser.add_option ('-p', '--path',
76                            dest="bin_path",
77                            action="store",
78                            metavar="LY_PATH",
79                            default="out/bin",
80                            help="directory where looking for LilyPond binaries")
81
82 options_parser.add_option ('-c', '--convert-ly',
83                            dest="convert_ly",
84                            action="store",
85                            metavar="CONVERT-LY",
86                            default="LY_PATH/convert-ly",
87                            help="convert-ly binary to use")
88
89 options_parser.add_option ('-l', '--lilypond-binary',
90                            dest="lilypond_bin",
91                            action="store",
92                            metavar="LILYPOND_BIN",
93                            default="LY_PATH/lilypond",
94                            help="lilypond binary to use")
95
96 (options, args) = options_parser.parse_args ()
97
98 if not os.path.isdir (options.top_source_dir):
99     sys.stderr.write ("Error: top source: %s: not a directory\n" % options.top_source_dir)
100     sys.exit (4)
101
102 lys_from_lsr = os.path.normpath (os.path.join (options.top_source_dir, lys_from_lsr))
103 new_lys = os.path.normpath (os.path.join (options.top_source_dir, new_lys))
104 sys.path.append (os.path.normpath (os.path.join (options.top_source_dir, 'python')))
105 import langdefs
106 texidoc_dirs = [
107     os.path.normpath (os.path.join (options.top_source_dir, 'Documentation', language_code, 'texidocs'))
108     for language_code in langdefs.LANGDICT]
109
110 if not os.path.isdir (lys_from_lsr):
111     sys.stderr.write ("Error: snippets path: %s: not a directory\n" % lys_from_lsr)
112     sys.exit (3)
113 if not os.path.isdir (new_lys):
114     sys.stderr.write ("Error: new snippets path: %s: not a directory\n" % lys_from_lsr)
115     sys.exit (3)
116
117 ly_output_ok = False
118 if os.path.isdir (options.ly_output):
119     ly_output = options.ly_output
120     ly_output_ok = True
121 elif os.path.exists (options.ly_output):
122     try:
123         os.unlink (options.ly_output)
124     except Exception as e:
125         sys.stderr.write ("Warning: could not delete file before creating directory: %s\n" % e)
126     else:
127         try:
128             os.makedirs (options.ly_output)
129         except Exception as e:
130             sys.stderr.write ("Warning: could not create directory: %s\n" % e)
131         else:
132             ly_output = options.ly_output
133             ly_output_ok = True
134 else:
135     try:
136         os.makedirs (options.ly_output)
137     except Exception as e:
138         sys.stderr.write ("Warning: could not create directory: %s\n" % e)
139     else:
140         ly_output = options.ly_output
141         ly_output_ok = True
142 if not ly_output_ok:
143     ly_output = tempfile.gettempdir ()
144     sys.stderr.write ("Warning: could not use or create directory %s, using default %s\n" % (options.ly_output, ly_output))
145
146 def exit_with_usage (n=0):
147     options_parser.print_help (sys.stderr)
148     sys.exit (n)
149
150 if len (args):
151     in_dir = args[0]
152     if not (os.path.isdir (in_dir)):
153         sys.stderr.write ("Error: %s: not a directory\n" % in_dir)
154         sys.exit (4)
155     if len (args) > 1:
156         exit_with_usage (2)
157 else:
158     in_dir = '.'
159
160 if options.convert_ly == "LY_PATH/convert-ly":
161     convert_ly = os.path.join (options.bin_path, "convert-ly")
162 else:
163     convert_ly = options.convert_ly
164 if not os.path.isfile (convert_ly):
165     sys.stderr.write ("Warning: %s: no such file")
166     convert_ly = "convert-ly"
167 if options.lilypond_bin == "LY_PATH/lilypond":
168     lilypond_bin = os.path.join (options.bin_path, "lilypond")
169 else:
170     lilypond_bin = options.lilypond_bin
171 if not os.path.isfile (lilypond_bin):
172     sys.stderr.write ("Warning: %s: no such file")
173     lilypond_bin = "lilypond"
174 sys.stderr.write ("Using %s, %s\n" % (convert_ly, lilypond_bin))
175
176 tags = os.listdir (in_dir)
177
178 unsafe = []
179 unconverted = []
180 notags_files = []
181
182 # mark the section that will be printed verbatim by lilypond-book
183 end_header_re = re.compile ('(\\header {.+?doctitle = ".+?})\n', re.M | re.S)
184
185 doctitle_re = re.compile (r'(doctitle[a-zA-Z_]{0,6}\s*=\s*")((?:\\"|[^"\n])*)"')
186 texinfo_q_re = re.compile (r'@q{(.*?)}')
187 texinfo_qq_re = re.compile (r'@qq{(.*?)}')
188 def doctitle_sub (title_match):
189     # Comma forbidden in Texinfo node name
190     title = title_match.group (2).replace (',', '')
191     title = texinfo_q_re.sub (r"`\1'", title)
192     title = texinfo_qq_re.sub (r'\"\1\"', title)
193     return title_match.group (1) + title + '"'
194
195 def mark_verbatim_section (ly_code):
196     return end_header_re.sub ('\\1 % begin verbatim\n\n', ly_code, 1)
197
198 # '% LSR' comments are to be stripped
199 lsr_comment_re = re.compile (r'\s*%+\s*LSR.*')
200 begin_header_re = re.compile (r'\\header\s*{', re.M)
201 ly_new_version_re = re.compile (r'\\version\s*"(.+?)"')
202 strip_white_spaces_re = re.compile (r'[ \t]+(?=\n)')
203
204 # add tags to ly files from LSR
205 def add_tags (ly_code, tags):
206     return begin_header_re.sub ('\\g<0>\n  lsrtags = "' + tags + '"\n',
207                                 ly_code, 1)
208
209 # for snippets from input/new, add message for earliest working version
210 def add_version (ly_code):
211     return '''%% Note: this file works from version ''' + \
212         ly_new_version_re.search (ly_code).group (1) + '\n'
213
214 def escape_backslashes_in_header(snippet):
215     # ASSUME: the \header exists.
216     header_char_number_start = snippet.find('\header {')
217     header_char_number_end = snippet.find('} % begin verbatim')
218
219     header = snippet[header_char_number_start:header_char_number_end]
220     # only one level of escaping happening here
221     # thanks to raw strings
222     new_header = re.sub(r"@code\{\\([a-zA-Z])", r"@code{\\\\\1", header)
223     escaped_snippet = (snippet[:header_char_number_start] +
224         new_header + snippet[header_char_number_end:])
225     return escaped_snippet
226
227 def copy_ly (srcdir, name, tags):
228     global unsafe
229     global unconverted
230     dest = os.path.join (lys_from_lsr, name)
231     tags = ', '.join (tags)
232     file_path = os.path.join (srcdir, name)
233     sys.stderr.write ("\nmakelsr.py: reading %s\n" % file_path)
234     s = open (file_path).read ()
235
236     s = doctitle_re.sub (doctitle_sub, s)
237     if "new" in srcdir:
238         s = LY_HEADER_NEW + add_version (s) + s
239     else:
240         s = LY_HEADER_LSR + add_tags (s, tags)
241
242     s = mark_verbatim_section (s)
243     s = lsr_comment_re.sub ('', s)
244     s = strip_white_spaces_re.sub ('', s)
245     s = escape_backslashes_in_header (s)
246     sys.stderr.write ("makelsr.py: writing %s\n" % dest)
247     open (dest, 'w').write (s)
248
249     e = os.system (convert_ly+(" -d -e '%s'" % dest))
250     if e:
251         unconverted.append (dest)
252     if os.path.exists (dest + '~'):
253         os.remove (dest + '~')
254     # no need to check snippets from Documentation/snippets/new
255     if not "new" in srcdir:
256         e = os.system (
257             "%s %s -o %s '%s'" %
258             (lilypond_bin, lilypond_flags, ly_output, dest))
259         if e:
260             unsafe.append (dest)
261
262 def read_source_with_dirs (src):
263     s = {}
264     l = {}
265     for tag in tags:
266         srcdir = os.path.join (src, tag)
267         l[tag] = set (map (os.path.basename,
268                            glob.glob (os.path.join (srcdir, '*.ly'))))
269         for f in l[tag]:
270             if f in s:
271                 s[f][1].append (tag)
272             else:
273                 s[f] = (srcdir, [tag])
274     return s, l
275
276
277 tags_re = re.compile ('lsrtags\\s*=\\s*"(.+?)"')
278
279 def read_source (src):
280     s = {}
281     l = dict ([(tag, set()) for tag in tags])
282     for f in glob.glob (os.path.join (src, '*.ly')):
283         basename = os.path.basename (f)
284         m = tags_re.search (open (f, 'r').read ())
285         if m:
286             file_tags = [tag.strip() for tag in m.group (1). split(',')]
287             s[basename] = (src, file_tags)
288             [l[tag].add (basename) for tag in file_tags if tag in tags]
289         else:
290             notags_files.append (f)
291     return s, l
292
293
294 def dump_file_list (file, file_list, update=False):
295     if update:
296         old_list = set (open (file, 'r').read ().splitlines ())
297         old_list.update (file_list)
298         new_list = list (old_list)
299     else:
300         new_list = file_list
301     f = open (file, 'w')
302     f.write ('\n'.join (sorted (new_list)) + '\n')
303
304 ## clean out existing lys and generated files
305 map (os.remove, glob.glob (os.path.join (lys_from_lsr, '*.ly')) +
306      glob.glob (os.path.join (lys_from_lsr, '*.snippet-list')))
307
308 # read LSR source where tags are defined by subdirs
309 snippets, tag_lists = read_source_with_dirs (in_dir)
310
311 # read input/new where tags are directly defined
312 s, l = read_source (new_lys)
313 snippets.update (s)
314 for t in tags:
315     tag_lists[t].update (l[t])
316
317 for (name, (srcdir, tags)) in snippets.items ():
318     copy_ly (srcdir, name, tags)
319 for (tag, file_set) in tag_lists.items ():
320     dump_file_list (os.path.join (lys_from_lsr, tag + '.snippet-list'),
321                     file_set, update=not(in_dir))
322 if unconverted:
323     sys.stderr.write ('These files could not be converted successfully by convert-ly:\n')
324     sys.stderr.write ('\n'.join (unconverted) + '\n\n')
325 if notags_files:
326     sys.stderr.write ('No tags could be found in these files:\n')
327     sys.stderr.write ('\n'.join (notags_files) + '\n\n')
328 if unsafe:
329     dump_file_list ('lsr-unsafe.txt', unsafe)
330     sys.stderr.write ('''
331
332 Unsafe files printed in lsr-unsafe.txt: CHECK MANUALLY!
333   git add %(lys_from_lsr)s/*.ly
334   xargs git diff HEAD < lsr-unsafe.txt
335
336 ''' % vars ())