2 # -*- coding: utf-8 -*-
15 from jinja2
import Environment
, Template
, FileSystemLoader
19 pp
= pprint
.PrettyPrinter(indent
=4)
21 program_name
= 'generate_oly_score';
22 settings_file
= 'oly_structure.def';
23 script_path
= os
.path
.dirname(__file__
);
25 ######################################################################
27 ######################################################################
29 help_text
= r
"""Usage: %(program_name)s [OPTIONS]... [DEF-FILE]
30 Create a complete file structure for a score using OrchestralLily.
31 If no definitions file it given, the file oly_structure.def is used
34 -h, --help print this help
35 -o, --output=DIR write output files to DIR (default: read from settings file)
39 sys
.stdout
.write (text
)
44 ######################################################################
46 ######################################################################
57 settings_file
= self
.load_options ();
58 self
.load (settings_file
);
59 self
.raw_data
["settings_file_path"] = settings_file
;
60 self
.raw_data
["settings_file"] = os
.path
.basename(settings_file
);
61 self
.init_template_env ();
64 def output_dir (self
):
67 return self
.raw_data
.get ("defaults", {});
68 def score_names (self
):
69 return self
.raw_data
.get ("scores", ["Score"]);
71 def get_score_settings (self
, id):
72 settings
= self
.globals.copy();
73 settings
.update (self
.defaults ());
74 settings
.update ({"name": id})
75 settings
.update (self
.raw_data
.get (id, {}));
76 self
.normalize_part_definitions (settings
);
79 def normalize_part_definitions (self
, score_settings
):
80 scorename
= score_settings
.get ("name", "Score");
81 parts
= score_settings
.get ("parts", [scorename
]);
83 for this_part
in parts
:
84 if isinstance (this_part
, basestring
):
85 this_part
= {"id": this_part
, "filename": this_part
}
86 elif not isinstance (this_part
, dict):
87 warning ("Invalid part list for score %a: %a" % (scorename
, p
));
89 if "id" not in this_part
:
90 this_part
["id"] = scorename
;
91 if "filename" not in this_part
:
92 this_part
["filename"] = this_part
["id"];
93 if "piece" not in this_part
:
94 this_part
["piece"] = this_part
["id"];
95 this_part
["score"] = scorename
;
96 this_part
.update (score_settings
);
97 result
.append (this_part
);
98 score_settings
["parts"] = result
;
101 return "latex" in self
.raw_data
;
102 def get_tex_settings (self
):
103 settings
= self
.globals.copy();
104 settings
.update (self
.defaults ());
105 settings
.update (self
.raw_data
.get ("latex", {}));
108 def get_score_parts (self
, score_settings
):
109 scorename
= score_settings
.get ("name", "Score");
110 parts
= score_settings
.get ("parts", [scorename
]);
114 if isinstance (this_part
, basestring
):
115 this_part
= {"id": this_part
, "filename": this_part
}
116 elif not isinstance (this_part
, dict):
117 warning ("Invalid part list for score %a: %a" % (scorename
, p
));
119 if "id" not in this_part
:
120 this_part
["id"] = scorename
;
121 if "filename" not in this_part
:
122 this_part
["filename"] = this_part
["id"];
123 if "piece" not in this_part
:
124 this_part
["piece"] = this_part
["id"];
125 this_part
["score"] = scorename
;
126 this_part
.update (score_settings
);
127 result
.append (this_part
);
130 def load_options (self
):
131 (self
.options
, self
.arguments
) = getopt
.getopt (sys
.argv
[1:], 'ho:', ['help', 'output='])
132 for opt
in self
.options
:
135 if o
== '-h' or o
== '--help':
136 help (help_text
% globals ())
137 elif o
== '-o' or o
== '--output':
140 raise Exception ('unknown option: ' + o
)
142 return self
.arguments
[0]
144 return "oly_structure.def";
146 def load (self
, filename
):
148 in_f
= codecs
.open (filename
, "r", "utf-8")
151 self
.raw_data
= eval(s
);
153 print ("Unable to load settings file '%s'. Exiting..." % file_name
)
155 except SyntaxError as ex
:
157 print ("Unable to interpret settings file '%s', it's syntax is invalid. Exiting..." % filename
);
160 self
.out_dir
= self
.raw_data
.get ("output_dir", self
.out_dir
) + "/";
162 def get_template_name (self
):
163 return self
.raw_data
.get ("template", "Full");
164 def get_template_names (self
, pattern
):
165 allfiles
= os
.listdir (self
.templatepath
);
168 if fnmatch
.fnmatch(f
, pattern
):
173 def init_template_env (self
):
176 templatename
= self
.get_template_name ();
177 self
.templatepath
= script_path
+ '/Templates/' + templatename
;
178 self
.template_env
= Environment (
179 loader
= FileSystemLoader(self
.templatepath
),
180 block_start_string
= '<$', block_end_string
= '$>',
181 variable_start_string
= '<<', variable_end_string
= '>>',
182 comment_start_string
= '<#', comment_end_string
= '#>',
183 #line_statement_prefix = '#'
185 def get_template (self
, template
):
186 return self
.template_env
.get_template (template
);
189 def init_arrays (self
):
191 self
.globals = self
.raw_data
.copy ();
192 del self
.globals["defaults"];
193 if "latex" in self
.globals:
194 del self
.globals["latex"];
195 for i
in self
.globals.get ("scores", []):
196 if i
in self
.globals:
199 def assemble_filename (self
, base
, id, name
, ext
):
200 parts
= [base
, id, name
];
203 return "_".join (parts
) + "." + ext
;
204 def assemble_movement_filename (self
, basename
, mvmnt
):
205 return self
.assemble_filename( basename
, "Music", mvmnt
, "ily");
206 def assemble_instrument_filename (self
, basename
, instr
):
207 return self
.assemble_filename( basename
, "Instrument", instr
, "ly");
208 def assemble_score_filename (self
, basename
, instr
):
209 return self
.assemble_filename( basename
, "Score", instr
, "ly");
210 def assemble_settings_filename (self
, basename
, s
):
211 return self
.assemble_filename( basename
, "Settings", s
, "ily");
212 def assemble_itex_filename (self
, basename
, filename
):
213 return self
.assemble_filename( "TeX", basename
, filename
, "itex");
214 def assemble_texscore_filename (self
, basename
, score
):
215 return self
.assemble_filename( "TeX", basename
, "Score_" + score
, "tex");
217 ######################################################################
219 ######################################################################
221 def write_file (path
, fname
, contents
):
222 file_name
= path
+ fname
;
224 if os
.path
.exists (file_name
):
227 dir = os
.path
.dirname (fn
);
228 if not os
.path
.exists (dir):
232 out_f
= codecs
.open (fn
, "w", "utf-8")
233 s
= out_f
.write(contents
)
237 print ("Unable to write to output file '%s'. Exiting..." % fn
)
240 # If the file already existed, check if the new file is identical.
241 if (fn
!= file_name
):
242 patchfile
= os
.path
.join (dir, "patches", os
.path
.basename(file_name
) + ".patch")
243 if os
.path
.exists (patchfile
):
245 retcode
= subprocess
.call("patch -Ns \""+ fn
+ "\" \"" + patchfile
+ "\"", shell
=True)
247 print >>sys
.stderr
, "Unable to apply patch to file \"", fn
, "\"."
249 print >>sys
.stderr
, "Execution failed:", e
251 if filecmp
.cmp (fn
, file_name
):
254 print ("A file %s already existed, created new file %s." % (os
.path
.basename(file_name
), os
.path
.basename(fn
)))
258 ######################################################################
259 # Creating movement files
260 ######################################################################
263 def generate_movement_files (score_name
, score_settings
, settings
):
265 for part_settings
in settings
.get_score_parts (score_settings
):
266 template
= settings
.get_template ("Lily_Music_Movement.ily");
267 basename
= part_settings
.get ("basename", score_name
);
268 filename
= part_settings
.get ("filename", score_name
+ "Part");
269 filename
= settings
.assemble_movement_filename (basename
, filename
);
270 write_file (settings
.out_dir
, filename
, template
.render (part_settings
));
271 parts_files
.append (filename
);
277 ######################################################################
278 # Creating instrumental score files
279 ######################################################################
281 def generate_instrument_files (score_name
, score_settings
, settings
):
282 instrument_files
= [];
283 settings_files
= set ();
284 instrument_settings
= score_settings
.copy ();
285 instrument_settings
["parts"] = settings
.get_score_parts (score_settings
);
287 template
= settings
.get_template ("Lily_Instrument.ly");
288 basename
= score_settings
.get ("basename", score_name
);
290 noscore_instruments
= score_settings
.get ("noscore_instruments", [])
291 for i
in score_settings
.get ("instruments", []):
292 if i
in noscore_instruments
:
294 instrument_settings
["instrument"] = i
;
295 if i
in score_settings
.get ("vocalvoices"):
296 instrument_settings
["settings"] = "VocalVoice";
298 instrument_settings
["settings"] = "Instrument";
299 settings_files
.add (instrument_settings
["settings"]);
300 filename
= settings
.assemble_instrument_filename (basename
, i
);
301 write_file (settings
.out_dir
, filename
, template
.render (instrument_settings
));
302 instrument_files
.append (filename
);
304 return (instrument_files
, settings_files
);
308 ######################################################################
309 # Creating score files
310 ######################################################################
314 "Particell": "Particell",
317 "OrganScore": "VocalScore",
318 "ChoralScore": "ChoralScore",
319 "LongScore": "FullScore",
320 "OriginalScore": "FullScore",
321 "Particell": "FullScore"
323 scores_cues
= ["ChoralScore", "VocalScore"];
325 def generate_score_files (score_name
, score_settings
, settings
):
327 settings_files
= set ();
328 s_settings
= score_settings
.copy ();
329 s_settings
["parts"] = settings
.get_score_parts (score_settings
);
330 s_settings
["fullscore"] = True;
332 template
= settings
.get_template ("Lily_Score.ly");
333 basename
= score_settings
.get ("basename", score_name
);
335 for s
in score_settings
.get ("scores", []):
336 fullsn
= score_name_map
.get (s
, (s
+ "Score"));
337 s_settings
["score"] = fullsn
;
338 s_settings
["nocues"] = fullsn
not in scores_cues
;
339 s_settings
["settings"] = settings_map
.get (fullsn
,fullsn
)
340 settings_files
.add (s_settings
["settings"]);
341 filename
= settings
.assemble_score_filename (basename
, s
);
342 write_file (settings
.out_dir
, filename
, template
.render (s_settings
));
343 score_files
.append (filename
);
345 return (score_files
, settings_files
);
349 ######################################################################
350 # Creating settings files
351 ######################################################################
353 def write_settings_file_if_exists (settings
, score_settings
, template
, filename
):
355 template
= settings
.get_template (template
);
356 basename
= score_settings
.get ("basename", "");
357 filename
= settings
.assemble_settings_filename (basename
, filename
);
358 write_file (settings
.out_dir
, filename
, template
.render (score_settings
));
360 except jinja2
.exceptions
.TemplateNotFound
:
363 def generate_settings_files (score_name
, score_settings
, settings
, settings_files
):
365 out_files
.append (write_settings_file_if_exists (settings
, score_settings
, "Lily_Settings_Global.ily", "Global" ));
366 out_files
.append (write_settings_file_if_exists (settings
, score_settings
, "Lily_Settings.ily", "" ));
368 for s
in settings_files
:
369 score_settings
["settings"] = s
.lower ();
370 out_files
.append (write_settings_file_if_exists (settings
, score_settings
, "Lily_Settings_Generic.ily", s
));
376 ######################################################################
377 # Generation of Lilypond Files
378 ######################################################################
380 def generate_scores (settings
):
382 for s
in settings
.score_names ():
383 score_settings
= settings
.get_score_settings (s
);
384 parts_files
= generate_movement_files (s
, score_settings
, settings
);
385 (instrument_files
, isettings_files
) = generate_instrument_files (s
, score_settings
, settings
);
386 (score_files
, ssettings_files
) = generate_score_files (s
, score_settings
, settings
);
387 included_settings_files
= ssettings_files | isettings_files
;
388 score_settings
["parts_files"] = parts_files
;
389 settings_files
= generate_settings_files (s
, score_settings
, settings
, included_settings_files
);
390 files
[s
] = {"settings": settings_files
,
391 "scores": score_files
,
392 "instruments": instrument_files
,
393 "parts": parts_files
};
398 ######################################################################
399 # Creating LaTeX files
400 ######################################################################
402 def write_itex_file (settings
, tex_settings
, template
, filename
):
403 template
= settings
.get_template (template
);
404 basename
= tex_settings
.get ("basename", "");
405 filename
= settings
.assemble_itex_filename (basename
, filename
);
406 write_file (settings
.out_dir
, filename
, template
.render (tex_settings
));
409 def write_texscore_file (settings
, tex_settings
, template
, score
):
410 template
= settings
.get_template (template
);
411 basename
= tex_settings
.get ("basename", "");
412 filename
= settings
.assemble_texscore_filename (basename
, score
);
413 write_file (settings
.out_dir
, filename
, template
.render (tex_settings
));
416 no_criticalcomments_scores
= ["Vocal", "Choral", "Organ"];
418 "Organ": "vocalscore",
419 "Choral": "choralscore",
420 "Vocal": "vocalscore",
423 "Original": "fullscore",
424 "Particell": "vocalscore",
425 "InstrumentalParts": "instrumentalparts",
429 def generate_tex_files (settings
, lily_files
):
432 tex_settings
= settings
.get_tex_settings ();
433 tex_settings
["lily_files"] = lily_files
;
436 instruments_scores
= [];
437 for s
in settings
.score_names ():
438 score_settings
= settings
.get_score_settings (s
);
439 instruments_scores
.append (score_settings
);
440 for p
in score_settings
.get ("scores", []):
441 score_map
[p
] = score_map
.get (p
, []) + [score_settings
];
442 tex_settings
["works"] = instruments_scores
;
444 tex_include_templates
= settings
.get_template_names("TeX_*.itex");
445 for t
in tex_include_templates
:
446 base
= re
.sub( r
'TeX_(.*)\.itex', r
'\1', t
);
447 tex_includes
.append (write_itex_file (settings
, tex_settings
, t
, base
));
449 for (score
, parts
) in score_map
.items ():
450 this_settings
= copy
.deepcopy (tex_settings
);
451 this_settings
["scoretype"] = score
;
452 this_settings
["scores"] = parts
;
453 tmpopts
= this_settings
.get ("tex_options", [])
454 tmpopts
.append (tex_options_map
.get (this_settings
["scoretype"], ""))
455 this_settings
["tex_options"] = tmpopts
456 if "createCriticalComments" not in tex_settings
:
457 this_settings
["createCriticalComments"] = score
not in no_criticalcomments_scores
;
458 tex_files
.append (write_texscore_file (settings
, this_settings
, "TeX_Score.tex", score
))
460 this_settings
= copy
.deepcopy (tex_settings
);
461 this_settings
["scoretype"] = "InstrumentalParts";
462 tmpopts
= this_settings
.get ("tex_options", [])
463 tmpopts
.append (tex_options_map
.get (this_settings
["scoretype"], ""))
464 this_settings
["tex_options"] = tmpopts
465 if "createCriticalComments" not in tex_settings
:
466 this_settings
["createCriticalComments"] = score
not in no_criticalcomments_scores
;
467 tex_files
.append (write_texscore_file (settings
, this_settings
, "TeX_Instruments.tex", "Instruments"))
469 return [tex_files
, tex_includes
];
473 ######################################################################
475 ######################################################################
477 def generate_make_files (settings
, lily_files
, tex_files
):
478 make_settings
= settings
.raw_data
.copy ();
480 if settings
.has_tex ():
481 tex_settings
= settings
.get_tex_settings ();
482 tex_settings
["includes"] = tex_files
[1];
483 tex_settings
["files"] = tex_files
[0];
484 make_settings
["latex"] = tex_settings
;
487 instruments_scores
= [];
489 for s
in settings
.score_names ():
491 score_settings
= settings
.get_score_settings (s
);
493 score_settings
["nr"] = nr
;
495 score_settings
["nr"] = "";
496 score_settings
["srcfiles"] = lily_files
[s
];
497 instruments_scores
.append (score_settings
);
498 for p
in score_settings
.get ("scores", []):
499 score_map
[p
] = score_map
.get (p
, []) + [score_settings
];
500 make_settings
["works"] = instruments_scores
;
502 template
= settings
.get_template ("Makefile");
503 #basename = tex_settings.get ("basename", "");
504 file = write_file (settings
.out_dir
, "Makefile", template
.render (make_settings
));
509 # movements = settings.get ("parts", {});
511 # replacements["instruments"] = string.join (settings.get ("instruments", []));
512 # replacements["scores"] = string.join (settings.get ("scores", []))## + " Instruments";
513 # replacements["srcfiles"] = string.join (src_files);
515 # write_file (output_dir + "Makefile", templates["Makefile"] % replacements);
517 # del replacements["instruments"]
518 # del replacements["scores"]
519 # del replacements["srcfiles"]
523 ######################################################################
524 # Link the orchestrallily package
525 ######################################################################
527 def generate_oly_link (settings
):
530 os
.symlink ("../"+script_path
, settings
.out_dir
+"orchestrallily")
534 ######################################################################
536 ######################################################################
539 settings
= Settings ();
540 print ("Creating OrchestralLily template in \"%s\", using template \"%s\"." %
541 (settings
.out_dir
, settings
.get_template_name () ));
543 print ("Creating Lilypond files")
544 lily_files
= generate_scores (settings
);
547 if settings
.has_tex ():
548 print ("Creating LaTeX files")
549 tex_files
= generate_tex_files (settings
, lily_files
);
550 print ("Creating OrchestralLily package link")
551 generate_oly_link (settings
);
552 print ("Creating Makefile")
553 generate_make_files (settings
, lily_files
, tex_files
);