Started moving the template generation to the new jinji2 template engine
[orchestrallily.git] / generate_oly_score.py
blob824e52b5c11307eccdf888cbe34d15d3ed06df2b
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
4 import sys
5 import os
6 import os.path
7 import getopt
8 import re
9 import filecmp
10 import string
11 import subprocess
12 import codecs
13 from jinja2 import Environment, Template, FileSystemLoader
15 program_name = 'generate_oly_score';
16 settings_file = 'oly_structure.def';
17 script_path = os.path.dirname(__file__);
19 ######################################################################
20 # Options handling
21 ######################################################################
23 help_text = r"""Usage: %(program_name)s [OPTIONS]... [DEF-FILE]
24 Create a complete file structure for a score using OrchestralLily.
25 If no definitions file it given, the file oly_structure.def is used
27 Options:
28 -h, --help print this help
29 -o, --output=DIR write output files to DIR (default: read from settings file)
30 """
32 def help (text):
33 sys.stdout.write (text)
34 sys.exit (0)
38 ######################################################################
39 # Settings
40 ######################################################################
42 class Settings:
43 options = {};
44 arguments = [];
45 globals={};
46 raw_data={};
47 out_dir = "";
48 template_env = None;
50 def __init__ (self):
51 settings_file = self.load_options ();
52 self.load (settings_file);
53 self.init_template_env ();
54 self.init_arrays ();
56 def output_dir (self):
57 return self.out_dir
58 def defaults (self):
59 return self.raw_data.get ("defaults", {});
60 def score_names (self):
61 return self.raw_data.get ("scores", ["Score"]);
63 def get_score_settings (self, id):
64 settings = self.globals.copy();
65 settings.update (self.defaults ());
66 settings.update ({"name": id})
67 settings.update (self.raw_data.get (id, {}));
68 # TODO: Normalize part definitions
69 return settings;
71 def has_tex (self):
72 return "tex" in self.raw_data;
73 def get_tex_settings (self):
74 settings = self.globals.copy();
75 settings.update (self.defaults ());
76 settings.update (self.raw_data.get ("latex", {}));
77 return settings;
79 def get_score_parts (self, score_settings):
80 scorename = score_settings.get ("name", "Score");
81 parts = score_settings.get ("parts", [scorename]);
82 result = [];
83 for p in parts:
84 this_part = p;
85 if isinstance (this_part, basestring):
86 this_part = {"id": this_part, "filename": this_part }
87 elif not isinstance (this_part, dict):
88 warning ("Invalid part list for score %a: %a" % (scorename, p));
89 return [];
90 if not this_part["id"]:
91 this_part["id"] = scorename;
92 if not this_part["filename"]:
93 this_part["filename"] = this_part["id"];
94 this_part["score"] = scorename;
95 this_part.update (score_settings);
96 result.append (this_part);
97 return result;
99 def load_options (self):
100 (self.options, self.arguments) = getopt.getopt (sys.argv[1:], 'ho:', ['help', 'output='])
101 for opt in self.options:
102 o = opt[0]
103 a = opt[1]
104 if o == '-h' or o == '--help':
105 help (help_text % globals ())
106 elif o == '-o' or o == '--output':
107 self.out_dir = a
108 else:
109 raise Exception ('unknown option: ' + o)
110 if self.arguments:
111 return self.arguments[0]
112 else:
113 return "oly_structure.def";
115 def load (self, filename):
116 try:
117 in_f = codecs.open (filename, "r", "utf-8")
118 s = in_f.read()
119 in_f.close()
120 self.raw_data = eval(s);
121 except IOError:
122 print ("Unable to load settings file '%s'. Exiting..." % file_name)
123 exit (-1);
124 except SyntaxError as ex:
125 print ex;
126 print ("Unable to interpret settings file '%s', it's syntax is invalid. Exiting..." % file_name);
127 exit (-1);
128 if not self.out_dir:
129 self.out_dir = self.raw_data.get ("output_dir", self.out_dir) + "/";
131 def get_template_name (self):
132 return self.raw_data.get ("template", "Full");
134 def init_template_env (self):
135 global program_name;
136 global script_path;
137 templatename = self.get_template_name ();
138 path = script_path + '/Templates/' + templatename;
139 self.template_env = Environment (
140 loader = FileSystemLoader(path),
141 block_start_string = '<$', block_end_string = '$>',
142 variable_start_string = '<<', variable_end_string = '>>',
143 comment_start_string = '<#', comment_end_string = '#>',
144 #line_statement_prefix = '#'
146 def get_template (self, template):
147 return self.template_env.get_template (template);
150 def init_arrays (self):
151 # Globals
152 self.globals = self.raw_data.copy ();
153 del self.globals["defaults"];
154 del self.globals["latex"];
155 for i in self.globals.get ("scores", []):
156 del self.globals[i];
158 def assemble_filename (self, base, id, name, ext):
159 return "_".join ([base, id, name]) + "." + ext;
160 def assemble_movement_filename (self, basename, mvmnt):
161 return self.assemble_filename( basename, "Music", mvmnt, "ily");
162 def assemble_instrument_filename (self, basename, instr):
163 return self.assemble_filename( basename, "Instrument", instr, "ly");
164 def assemble_score_filename (self, basename, instr):
165 return self.assemble_filename( basename, "Score", instr, "ly");
166 def assemble_settings_filename (self, basename, s):
167 return self.assemble_filename( basename, "Settings", s, "ily");
170 ######################################################################
171 # File Writing
172 ######################################################################
174 def write_file (path, fname, contents):
175 file_name = path + fname;
176 fn = file_name
177 if os.path.exists (file_name):
178 fn += ".new"
180 dir = os.path.dirname (fn);
181 if not os.path.exists (dir):
182 os.mkdir (dir)
184 try:
185 out_f = codecs.open (fn, "w", "utf-8")
186 s = out_f.write(contents)
187 out_f.close()
188 except IOError:
189 print ("Unable to write to output file '%s'. Exiting..." % fn)
190 exit (-1);
192 # If the file already existed, check if the new file is identical.
193 if (fn != file_name):
194 patchfile = os.path.join (dir, "patches", os.path.basename(file_name) + ".patch")
195 if os.path.exists (patchfile):
196 try:
197 retcode = subprocess.call("patch -Ns \""+ fn+ "\" \"" + patchfile + "\"", shell=True)
198 if retcode < 0:
199 print >>sys.stderr, "Unable to apply patch to file \"", fn, "\"."
200 except OSError, e:
201 print >>sys.stderr, "Execution failed:", e
203 if filecmp.cmp (fn, file_name):
204 os.unlink (fn);
205 else:
206 print ("A file %s already existed, created new file %s." % (os.path.basename(file_name), os.path.basename(fn)))
210 ######################################################################
211 # Creating movement files
212 ######################################################################
215 def generate_movement_files (score_name, score_settings, settings):
216 parts_files = [];
217 for part_settings in settings.get_score_parts (score_settings):
218 template = settings.get_template ("Lily_Music_Movement.ily");
219 basename = part_settings.get ("basename", score_name);
220 filename = part_settings.get ("filename", score_name + "Part");
221 filename = settings.assemble_movement_filename (basename, filename);
222 write_file (settings.out_dir, filename, template.render (part_settings));
223 parts_files.append (filename);
225 return parts_files;
229 ######################################################################
230 # Creating instrumental score files
231 ######################################################################
233 def generate_instrument_files (score_name, score_settings, settings):
234 instrument_files = [];
235 settings_files = set ();
236 instrument_settings = score_settings.copy ();
237 instrument_settings["parts"] = settings.get_score_parts (score_settings);
239 template = settings.get_template ("Lily_Instrument.ly");
240 basename = score_settings.get ("basename", score_name );
242 for i in score_settings.get ("instruments", []):
243 instrument_settings["instrument"] = i;
244 if i in score_settings.get ("vocalvoices"):
245 instrument_settings["settings"] = "VocalVoice";
246 else:
247 instrument_settings["settings"] = "Instrument";
248 settings_files.add (instrument_settings["settings"]);
249 filename = settings.assemble_instrument_filename (basename, i);
250 write_file (settings.out_dir, filename, template.render (instrument_settings));
251 instrument_files.append (filename);
253 return (instrument_files, settings_files);
257 ######################################################################
258 # Creating score files
259 ######################################################################
262 score_name_map = {
263 "Particell": "Particell",
265 settings_map = {
266 "OrganScore": "VocalScore",
267 "ChoralScore": "ChoralScore",
268 "LongScore": "FullScore",
269 "OriginalScore": "FullScore",
270 "Particell": "FullScore"
272 scores_cues = ["ChoralScore", "VocalScore"];
274 def generate_score_files (score_name, score_settings, settings):
275 score_files = [];
276 settings_files = set ();
277 s_settings = score_settings.copy ();
278 s_settings["parts"] = settings.get_score_parts (score_settings);
279 s_settings["fullscore"] = True;
281 template = settings.get_template ("Lily_Score.ly");
282 basename = score_settings.get ("basename", score_name );
284 for s in score_settings.get ("scores", []):
285 fullsn = score_name_map.get (s, (s + "Score"));
286 s_settings["score"] = fullsn;
287 s_settings["nocues"] = fullsn in scores_cues;
288 s_settings["settings"] = settings_map.get (fullsn,fullsn)
289 settings_files.add (s_settings["settings"]);
290 filename = settings.assemble_score_filename (basename, s);
291 write_file (settings.out_dir, filename, template.render (s_settings));
292 score_files.append (filename);
294 return (score_files, settings_files);
298 ######################################################################
299 # Creating settings files
300 ######################################################################
302 def write_settings_file (settings, score_settings, template, filename):
303 template = settings.get_template (template);
304 basename = score_settings.get ("basename", "");
305 filename = settings.assemble_settings_filename (basename, filename);
306 write_file (settings.out_dir, filename, template.render (score_settings));
307 return filename;
309 def generate_settings_files (score_name, score_settings, settings, settings_files):
310 out_files = [];
311 out_files.append (write_settings_file (settings, score_settings, "Lily_Settings_Global.ily", "Global" ));
312 out_files.append (write_settings_file (settings, score_settings, "Lily_Settings.ily", "" ));
314 for s in settings_files:
315 score_settings["settings"] = s.lower ();
316 out_files.append (write_settings_file (settings, score_settings, "Lily_Settings_Generic.ily", s));
318 return out_files;
322 ######################################################################
323 # Generation of Lilypond Files
324 ######################################################################
326 def generate_scores (settings):
327 files = {}
328 for s in settings.score_names ():
329 score_settings = settings.get_score_settings (s);
330 parts_files = generate_movement_files (s, score_settings, settings);
331 (instrument_files, isettings_files) = generate_instrument_files (s, score_settings, settings);
332 (score_files, ssettings_files) = generate_score_files (s, score_settings, settings);
333 included_settings_files = ssettings_files | isettings_files;
334 score_settings["parts_files"] = parts_files;
335 settings_files = generate_settings_files (s, score_settings, settings, included_settings_files );
336 files["s"] = {"settings": settings_files,
337 "scores": score_files,
338 "instruments": instrument_files,
339 "parts": parts_files };
340 return files
344 ######################################################################
345 # Creating LaTeX files
346 ######################################################################
348 def write_tex_file (score_settings, template, filename):
349 #def write_tex_file (settings, template,
350 #score_settings, template, filename):
351 template = settings.get_template (template);
352 basename = score_settings.get ("basename", "");
353 filename = settings.assemble_settings_filename (basename, filename);
354 write_file (settings.out_dir, filename, template.render (score_settings));
355 return filename;
357 #def write_tex_file (fn, contents):
358 # write_file (output_dir + "/"+ fn, contents );
359 # tex_files.append (fn)
361 #packageoptions = {
362 # "Vocal": ["vocalscore"],
365 def generate_tex_files (settings, lily_files):
366 pass
367 files = {};
368 tex_settings = settings.get_tex_settings ();
369 tex_settings["lily_files"] = lily_files;
371 files.append (write_tex_file ("TeX_Include_About.itex", "Include_About"));
373 for s in settings.score_names ():
374 tex_files = [];
375 score_settings = settings.get_score_settings (s);
376 parts_files = generate_movement_files (s, score_settings, settings);
377 (instrument_files, isettings_files) = generate_instrument_files (s, score_settings, settings);
378 (score_files, ssettings_files) = generate_score_files (s, score_settings, settings);
379 included_settings_files = ssettings_files | isettings_files;
380 score_settings["parts_files"] = parts_files;
381 settings_files = generate_settings_files (s, score_settings, settings, included_settings_files );
382 files["s"] = {"settings": settings_files,
383 "scores": score_files,
384 "instruments": instrument_files,
385 "parts": parts_files };
386 return files
388 #def generate_tex_files (settings, templates, replacements):
389 # basename = replacements["tex_basename"]
390 # write_tex_file ("TeX_" + basename + "_Settings.itex", templates["itex_settings"] % replacements);
391 # write_tex_file ("TeX_" + basename + "_Include_Coverpage.itex", templates["itex_coverpage"] % replacements);
392 # write_tex_file ("TeX_" + basename + "_Include_Backpage.itex", templates["itex_backpage"] % replacements);
393 # write_tex_file ("TeX_" + basename + "_Include_About.itex", templates["itex_about"] % replacements);
394 # write_tex_file ("TeX_" + basename + "_Include_Leben.itex", templates["itex_leben"] % replacements);
395 # write_tex_file ("TeX_" + basename + "_Include_Todo.itex", templates["itex_todo"] % replacements);
396 # write_tex_file ("TeX_" + basename + "_Include_Preface_Long.itex", templates["itex_preface_long"] % replacements);
397 # write_tex_file ("TeX_" + basename + "_Include_Preface_Short.itex", templates["itex_preface_short"] % replacements);
399 # txt = ""
400 # kb = ""
401 # for m in settings.get ("parts",[]):
402 # if type(m) == dict:
403 # replacements["movement"] = m.get ("name", "TODO")
404 # replacements["movementname"] = m.get ("piece", replacements["movement"])
405 # else:
406 # replacements["movement"] = m
407 # replacements["movementname"] = m
408 # txt += (templates["itex_snippet_textmovement"] % replacements);
409 # kb += (templates["itex_snippet_kritbericht"] % replacements);
410 # del replacements["movement"]
411 # del replacements["movementname"]
412 # replacements["movementtexts"] = txt
413 # replacements["kritbericht_movements"] = kb
414 # write_tex_file ("TeX_" + basename + "_Include_Text.itex", templates["itex_text"] % replacements);
415 # write_tex_file ("TeX_" + basename + "_Include_KritBericht.itex", templates["itex_kritbericht"] % replacements);
416 # del replacements["movementtexts"]
417 # del replacements["kritbericht_movements"]
419 # ### Contents and Material:
420 # lst = ""
421 # for i in settings.get ("scores", []):
422 # replacements["type"] = i + "Score";
423 # lst += templates["itex_snippet_material"] % replacements
424 # replacements["type"] = "Instruments"
425 # lst += templates["itex_snippet_material"] % replacements
426 # for i in settings.get ("instruments", []):
427 # replacements["type"] = i;
428 # lst += templates["itex_snippet_material"] % replacements
429 # del replacements["type"]
430 # replacements["material_list"] = lst;
431 # write_tex_file ("TeX_" + basename + "_Include_ContentsMaterial.itex", templates["itex_contents"] % replacements);
432 # del replacements["material_list"]
434 # ### Score files
435 # for s in settings.get ("scores", []):
436 # opts = ",".join (packageoptions.get (s, []));
437 # if opts:
438 # opts = "["+opts+"]";
439 # replacements["ekopts"] = opts;
440 # replacements["scoretype"] = s;
441 # write_tex_file ("TeX_" + basename + "_Score_" + s + ".tex", templates["tex_score"] % replacements);
442 # del replacements["scoretype"];
444 # ### Orchestra material
445 # replacements["scoretype"] = "InstrumentalParts"
446 # lst = ""
447 # for i in settings.get ("instruments", []):
448 # opts = ",".join (packageoptions.get (s, []));
449 # if opts:
450 # opts = "["+opts+"]";
451 # replacements["ekopts"] = opts;
452 # replacements["instrument"] = i
453 # lst += templates["itex_snippet_instrument"] % replacements;
454 # del replacements["instrument"]
455 # replacements["instrumentscores"] = lst;
456 # write_tex_file ("TeX_" + basename + "_Score_Instruments.tex", templates["tex_instruments"] % replacements);
457 # del replacements["instrumentscores"];
458 # del replacements["ekopts"];
462 ######################################################################
463 # Creating Make files
464 ######################################################################
466 #def generate_make_files (settings, templates, replacements):
467 # movements = settings.get ("parts", {});
469 # replacements["instruments"] = string.join (settings.get ("instruments", []));
470 # replacements["scores"] = string.join (settings.get ("scores", []))## + " Instruments";
471 # replacements["srcfiles"] = string.join (src_files);
473 # write_file (output_dir + "Makefile", templates["Makefile"] % replacements);
475 # del replacements["instruments"]
476 # del replacements["scores"]
477 # del replacements["srcfiles"]
478 # return;
481 ######################################################################
482 # Link the orchestrallily package
483 ######################################################################
485 def generate_oly_link (settings):
486 global script_path;
487 try:
488 os.symlink ("../"+script_path, settings.out_dir+"orchestrallily")
489 except OSError:
490 pass
492 ######################################################################
493 # Main function
494 ######################################################################
496 def main ():
497 settings = Settings ();
498 print ("Creating OrchestralLily template in \"%s\", using template \"%s\"." %
499 (settings.out_dir, settings.get_template_name () ));
501 print ("Creating Lilypond files")
502 lily_files = generate_scores (settings);
504 if settings.has_tex ():
505 print ("Creating LaTeX files")
506 tex_files = generate_tex_files (settings, lily_files);
507 print ("Creating OrchestralLily package link")
508 generate_oly_link (settings);
509 print ("Creating Makefile")
510 # generate_make_files (settings, templates, replacements);
512 main ();