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
)
45 def import_file (filename
):
48 in_f
= codecs
.open (filename
, "r", "utf-8")
53 print ("Unable to load file '%s'. Exiting..." % filename
)
55 except SyntaxError as ex
:
57 print ("Unable to interpret settings file '%s', it's syntax is invalid. Exiting..." % filename
);
62 # Load the score type/number definitions from the oly directory
63 score_types
= import_file (os
.path
.join(sys
.path
[0], "oly_defs.py"));
67 ######################################################################
69 ######################################################################
80 settings_file
= self
.load_options ();
81 self
.load (settings_file
);
82 self
.raw_data
["settings_file_path"] = settings_file
;
83 self
.raw_data
["settings_file"] = os
.path
.basename(settings_file
);
84 self
.init_template_env ();
87 def output_dir (self
):
90 return self
.raw_data
.get ("defaults", {});
91 def score_names (self
):
92 return self
.raw_data
.get ("scores", ["Score"]);
94 def get_score_settings (self
, id):
95 settings
= self
.globals.copy();
96 settings
.update (self
.defaults ());
97 settings
.update ({"name": id})
98 settings
.update (self
.raw_data
.get (id, {}));
99 self
.normalize_part_definitions (settings
);
102 def normalize_part_definitions (self
, score_settings
):
103 scorename
= score_settings
.get ("name", "Score");
104 parts
= score_settings
.get ("parts", [scorename
]);
106 for this_part
in parts
:
107 if isinstance (this_part
, basestring
):
108 this_part
= {"id": this_part
, "filename": this_part
}
109 elif not isinstance (this_part
, dict):
110 warning ("Invalid part list for score %a: %a" % (scorename
, p
));
112 if "id" not in this_part
:
113 this_part
["id"] = scorename
;
114 if "filename" not in this_part
:
115 this_part
["filename"] = this_part
["id"];
116 if "piece" not in this_part
:
117 this_part
["piece"] = this_part
["id"];
118 this_part
["score"] = scorename
;
119 this_part
.update (score_settings
);
120 result
.append (this_part
);
121 score_settings
["parts"] = result
;
124 return "latex" in self
.raw_data
;
125 def get_tex_settings (self
):
126 settings
= self
.globals.copy();
127 settings
.update (self
.defaults ());
128 settings
.update (self
.raw_data
.get ("latex", {}));
131 def get_score_parts (self
, score_settings
):
132 scorename
= score_settings
.get ("name", "Score");
133 parts
= score_settings
.get ("parts", [scorename
]);
137 if isinstance (this_part
, basestring
):
138 this_part
= {"id": this_part
, "filename": this_part
}
139 elif not isinstance (this_part
, dict):
140 warning ("Invalid part list for score %a: %a" % (scorename
, p
));
142 if "id" not in this_part
:
143 this_part
["id"] = scorename
;
144 if "filename" not in this_part
:
145 this_part
["filename"] = this_part
["id"];
146 if "piece" not in this_part
:
147 this_part
["piece"] = this_part
["id"];
148 this_part
["score"] = scorename
;
149 this_part
.update (score_settings
);
150 result
.append (this_part
);
153 def load_options (self
):
154 (self
.options
, self
.arguments
) = getopt
.getopt (sys
.argv
[1:], 'ho:', ['help', 'output='])
155 for opt
in self
.options
:
158 if o
== '-h' or o
== '--help':
159 help (help_text
% globals ())
160 elif o
== '-o' or o
== '--output':
163 raise Exception ('unknown option: ' + o
)
165 return self
.arguments
[0]
167 return "oly_structure.def";
169 def load (self
, filename
):
170 self
.raw_data
= import_file (filename
)
172 self
.out_dir
= self
.raw_data
.get ("output_dir", self
.out_dir
) + "/";
174 def get_template_name (self
):
175 return self
.raw_data
.get ("template", "Full");
176 def get_template_names (self
, pattern
):
177 allfiles
= os
.listdir (self
.templatepath
);
180 if fnmatch
.fnmatch(f
, pattern
):
185 def init_template_env (self
):
188 templatename
= self
.get_template_name ();
189 self
.defaulttemplatepath
= script_path
+ '/Templates/default';
190 self
.templatepath
= script_path
+ '/Templates/' + templatename
;
191 self
.template_env
= Environment (
192 loader
= FileSystemLoader([self
.templatepath
,self
.defaulttemplatepath
]),
193 block_start_string
= '<$', block_end_string
= '$>',
194 variable_start_string
= '<<', variable_end_string
= '>>',
195 comment_start_string
= '<#', comment_end_string
= '#>',
196 #line_statement_prefix = '#'
198 def get_template (self
, template
):
199 return self
.template_env
.get_template (template
);
202 def init_arrays (self
):
204 self
.globals = self
.raw_data
.copy ();
205 del self
.globals["defaults"];
206 if "latex" in self
.globals:
207 del self
.globals["latex"];
208 for i
in self
.globals.get ("scores", []):
209 if i
in self
.globals:
212 def assemble_filename (self
, base
, id, name
, ext
):
213 parts
= [base
, id, name
];
216 return "_".join (parts
) + "." + ext
;
217 def assemble_movement_filename (self
, basename
, mvmnt
):
218 return self
.assemble_filename( basename
, "Music", mvmnt
, "ily");
219 def assemble_instrument_filename (self
, basename
, instr
):
220 return self
.assemble_filename( basename
, "Instrument", instr
, "ly");
221 def assemble_score_filename (self
, basename
, instr
):
222 return self
.assemble_filename( basename
, "Score", instr
, "ly");
223 def assemble_settings_filename (self
, basename
, s
):
224 return self
.assemble_filename( basename
, "Settings", s
, "ily");
225 def assemble_itex_filename (self
, basename
, filename
):
226 return self
.assemble_filename( "TeX", basename
, filename
, "itex");
227 def assemble_texscore_filename (self
, basename
, score
):
228 return self
.assemble_filename( "TeX", basename
, "Score_" + score
, "tex");
230 ######################################################################
232 ######################################################################
234 def write_file (path
, fname
, contents
):
235 file_name
= path
+ fname
;
237 if os
.path
.exists (file_name
):
240 dir = os
.path
.dirname (fn
);
241 if not os
.path
.exists (dir):
245 out_f
= codecs
.open (fn
, "w", "utf-8")
246 s
= out_f
.write(contents
)
250 print ("Unable to write to output file '%s'. Exiting..." % fn
)
253 # If the file already existed, check if the new file is identical.
254 if (fn
!= file_name
):
255 patchfile
= os
.path
.join (dir, "patches", os
.path
.basename(file_name
) + ".patch")
256 if os
.path
.exists (patchfile
):
258 retcode
= subprocess
.call("patch -Ns \""+ fn
+ "\" \"" + patchfile
+ "\"", shell
=True)
260 print >>sys
.stderr
, "Unable to apply patch to file \"", fn
, "\"."
262 print >>sys
.stderr
, "Execution failed:", e
264 if filecmp
.cmp (fn
, file_name
):
267 print ("A file %s already existed, created new file %s." % (os
.path
.basename(file_name
), os
.path
.basename(fn
)))
271 ######################################################################
272 # Creating movement files
273 ######################################################################
276 def generate_movement_files (score_name
, score_settings
, settings
):
278 for part_settings
in settings
.get_score_parts (score_settings
):
279 template
= settings
.get_template ("Lily_Music_Movement.ily");
280 basename
= part_settings
.get ("basename", score_name
);
281 filename
= part_settings
.get ("filename", score_name
+ "Part");
282 filename
= settings
.assemble_movement_filename (basename
, filename
);
283 write_file (settings
.out_dir
, filename
, template
.render (part_settings
));
284 parts_files
.append (filename
);
290 ######################################################################
291 # Creating instrumental score files
292 ######################################################################
294 def generate_instrument_files (score_name
, score_settings
, settings
):
295 instrument_files
= [];
296 settings_files
= set ();
297 instrument_settings
= score_settings
.copy ();
298 instrument_settings
["parts"] = settings
.get_score_parts (score_settings
);
300 template
= settings
.get_template ("Lily_Instrument.ly");
301 basename
= score_settings
.get ("basename", score_name
);
303 noscore_instruments
= score_settings
.get ("noscore_instruments", [])
304 for i
in score_settings
.get ("instruments", []):
305 if i
in noscore_instruments
:
307 instrument_settings
["instrument"] = i
;
308 if i
in score_settings
.get ("vocalvoices"):
309 instrument_settings
["settings"] = "VocalVoice";
311 instrument_settings
["settings"] = "Instrument";
312 settings_files
.add (instrument_settings
["settings"]);
313 filename
= settings
.assemble_instrument_filename (basename
, i
);
314 write_file (settings
.out_dir
, filename
, template
.render (instrument_settings
));
315 instrument_files
.append (filename
);
317 return (instrument_files
, settings_files
);
321 ######################################################################
322 # Creating score files
323 ######################################################################
327 "Particell": "Particell",
330 "OrganScore": "VocalScore",
331 "ChoralScore": "ChoralScore",
332 "LongScore": "FullScore",
333 "OriginalScore": "FullScore",
334 "Particell": "FullScore"
336 scores_cues
= ["ChoralScore", "VocalScore"];
338 def generate_score_files (score_name
, score_settings
, settings
):
340 settings_files
= set ();
341 s_settings
= score_settings
.copy ();
342 s_settings
["parts"] = settings
.get_score_parts (score_settings
);
343 s_settings
["fullscore"] = True;
345 template
= settings
.get_template ("Lily_Score.ly");
346 basename
= score_settings
.get ("basename", score_name
);
348 for s
in score_settings
.get ("scores", []):
349 fullsn
= score_name_map
.get (s
, (s
+ "Score"));
350 s_settings
["score"] = fullsn
;
351 s_settings
["nocues"] = fullsn
not in scores_cues
;
352 s_settings
["settings"] = settings_map
.get (fullsn
,fullsn
)
353 settings_files
.add (s_settings
["settings"]);
354 filename
= settings
.assemble_score_filename (basename
, s
);
355 write_file (settings
.out_dir
, filename
, template
.render (s_settings
));
356 score_files
.append (filename
);
358 return (score_files
, settings_files
);
362 ######################################################################
363 # Creating settings files
364 ######################################################################
366 def write_settings_file_if_exists (settings
, score_settings
, template
, filename
):
368 template
= settings
.get_template (template
);
369 basename
= score_settings
.get ("basename", "");
370 filename
= settings
.assemble_settings_filename (basename
, filename
);
371 write_file (settings
.out_dir
, filename
, template
.render (score_settings
));
373 except jinja2
.exceptions
.TemplateNotFound
as e
:
376 def generate_settings_files (score_name
, score_settings
, settings
, settings_files
):
378 out_files
.append (write_settings_file_if_exists (settings
, score_settings
, "Lily_Settings_Global.ily", "Global" ));
379 out_files
.append (write_settings_file_if_exists (settings
, score_settings
, "Lily_Settings.ily", "" ));
381 for s
in settings_files
:
382 score_settings
["settings"] = s
.lower ();
383 out_files
.append (write_settings_file_if_exists (settings
, score_settings
, "Lily_Settings_Generic.ily", s
));
389 ######################################################################
390 # Generation of Lilypond Files
391 ######################################################################
393 def generate_scores (settings
):
395 for s
in settings
.score_names ():
396 score_settings
= settings
.get_score_settings (s
);
397 parts_files
= generate_movement_files (s
, score_settings
, settings
);
398 (instrument_files
, isettings_files
) = generate_instrument_files (s
, score_settings
, settings
);
399 (score_files
, ssettings_files
) = generate_score_files (s
, score_settings
, settings
);
400 included_settings_files
= ssettings_files | isettings_files
;
401 score_settings
["parts_files"] = parts_files
;
402 settings_files
= generate_settings_files (s
, score_settings
, settings
, included_settings_files
);
403 files
[s
] = {"settings": settings_files
,
404 "scores": score_files
,
405 "instruments": instrument_files
,
406 "parts": parts_files
};
411 ######################################################################
412 # Creating LaTeX files
413 ######################################################################
415 def write_itex_file (settings
, tex_settings
, template
, filename
):
416 template
= settings
.get_template (template
);
417 basename
= tex_settings
.get ("basename", "");
418 filename
= settings
.assemble_itex_filename (basename
, filename
);
419 write_file (settings
.out_dir
, filename
, template
.render (tex_settings
));
422 def write_texscore_file (settings
, tex_settings
, template
, score
):
423 template
= settings
.get_template (template
);
424 basename
= tex_settings
.get ("basename", "");
425 filename
= settings
.assemble_texscore_filename (basename
, score
);
426 write_file (settings
.out_dir
, filename
, template
.render (tex_settings
));
429 no_criticalcomments_scores
= ["Vocal", "Choral", "Organ"];
431 "Organ": "vocalscore",
432 "Choral": "choralscore",
433 "Vocal": "vocalscore",
436 "Original": "fullscore",
437 "Particell": "vocalscore",
438 "InstrumentalParts": "instrumentalparts",
442 def generate_tex_files (settings
, lily_files
):
445 tex_settings
= settings
.get_tex_settings ();
446 tex_settings
["lily_files"] = lily_files
;
449 instruments_scores
= [];
450 for s
in settings
.score_names ():
451 score_settings
= settings
.get_score_settings (s
);
452 instruments_scores
.append (score_settings
);
453 for p
in score_settings
.get ("scores", []):
454 score_map
[p
] = score_map
.get (p
, []) + [score_settings
];
455 for i
in tex_settings
.get ("instruments", []):
456 if i
in tex_settings
.get ("instrumentalscores", []):
457 score_map
[i
] = score_map
.get (i
, []) + [score_settings
];
458 tex_settings
["works"] = instruments_scores
;
460 tex_include_templates
= settings
.get_template_names("TeX_*.itex");
461 for t
in tex_include_templates
:
462 base
= re
.sub( r
'TeX_(.*)\.itex', r
'\1', t
);
463 tex_includes
.append (write_itex_file (settings
, tex_settings
, t
, base
));
465 for (score
, parts
) in score_map
.items ():
466 this_settings
= copy
.deepcopy (tex_settings
);
467 this_settings
["scoretype"] = score
;
468 this_settings
["scores"] = parts
;
469 #this_settings["scorebasetype"] = "Score";
470 #this_settings["scorenamebase"] = "ScoreType";
471 tmpopts
= this_settings
.get ("tex_options", [])
472 tmpopts
.append (tex_options_map
.get (this_settings
["scoretype"], ""))
473 this_settings
["tex_options"] = tmpopts
474 if "createCriticalComments" not in tex_settings
:
475 this_settings
["createCriticalComments"] = score
not in no_criticalcomments_scores
;
476 this_settings
["is_instrument"] = score
in tex_settings
.get ("instruments", []);
477 #this_settings["scorebasetype"] = "Instrument";
478 #this_settings["scorenamebase"] = "Name";
479 tex_files
.append (write_texscore_file (settings
, this_settings
, "TeX_Score.tex", score
))
482 # Create the tex instruments file only if we have instruments!
483 if set(this_settings
["instruments"]) != set(this_settings
["noscore_instruments"]):
484 this_settings
= copy
.deepcopy (tex_settings
);
485 this_settings
["scoretype"] = "InstrumentalParts";
486 tmpopts
= this_settings
.get ("tex_options", [])
487 tmpopts
.append (tex_options_map
.get (this_settings
["scoretype"], ""))
488 this_settings
["tex_options"] = tmpopts
489 if "createCriticalComments" not in tex_settings
:
490 this_settings
["createCriticalComments"] = score
not in no_criticalcomments_scores
;
491 tex_files
.append (write_texscore_file (settings
, this_settings
, "TeX_Instruments.tex", "Instruments"))
493 print " No separate instrumental scores, not creating instruments tex file."
495 return [tex_files
, tex_includes
];
499 ######################################################################
501 ######################################################################
503 def generate_make_files (settings
, lily_files
, tex_files
):
504 make_settings
= settings
.raw_data
.copy ();
506 if settings
.has_tex ():
507 tex_settings
= settings
.get_tex_settings ();
508 tex_settings
["includes"] = tex_files
[1];
509 tex_settings
["files"] = tex_files
[0];
510 make_settings
["latex"] = tex_settings
;
513 instruments_scores
= [];
515 for s
in settings
.score_names ():
517 score_settings
= settings
.get_score_settings (s
);
519 score_settings
["nr"] = nr
;
521 score_settings
["nr"] = "";
522 score_settings
["srcfiles"] = lily_files
[s
];
523 instruments_scores
.append (score_settings
);
524 for p
in score_settings
.get ("scores", []):
525 score_map
[p
] = score_map
.get (p
, []) + [score_settings
];
526 make_settings
["works"] = instruments_scores
;
528 template
= settings
.get_template ("Makefile");
529 #basename = tex_settings.get ("basename", "");
530 file = write_file (settings
.out_dir
, "Makefile", template
.render (make_settings
));
536 ######################################################################
537 # Creating webshop_descriptions.def
538 ######################################################################
540 def generate_webshop_files (settings
, lily_files
, tex_files
):
541 webshop_settings
= settings
.raw_data
.copy ();
542 template
= settings
.get_template ("webshop_descriptions.def");
543 #basename = tex_settings.get ("basename", "");
544 #print pp.pprint(settings.raw_data);
545 #print pp.pprint(settings.score_names ())
549 for s
in settings
.score_names ():
550 score_settings
= settings
.get_score_settings (s
);
551 noscore_instruments
= score_settings
.get ("noscore_instruments", [])
552 for i
in score_settings
.get ("scores", []) + ["Instruments"] + score_settings
.get ("instruments", []):
553 if i
in noscore_instruments
:
555 score_info
= score_types
.get (i
, {});
556 score_type
= score_info
.get ("Name", "");
557 score_id
= score_info
.get ("Number", "XXX");
559 scores
.append({"id": sid
, "sku": score_settings
.get ("scorenumber")+"-"+score_id
, "type": score_type
});
561 webshop_settings
["webshop_editions"] = sorted (scores
, key
=lambda k
: k
.get("id", 0));
563 webshop_settings
.update (webshop_settings
.get("defaults", {}));
564 file = write_file (settings
.out_dir
, "webshop_descriptions.def", template
.render (webshop_settings
));
568 ######################################################################
569 # Link the orchestrallily package
570 ######################################################################
572 def generate_oly_link (settings
):
575 os
.symlink ("../"+script_path
, settings
.out_dir
+"orchestrallily")
579 ######################################################################
581 ######################################################################
584 settings
= Settings ();
585 print ("Creating OrchestralLily template in \"%s\", using template \"%s\"." %
586 (settings
.out_dir
, settings
.get_template_name () ));
588 print ("Creating Lilypond files")
589 lily_files
= generate_scores (settings
);
592 if settings
.has_tex ():
593 print ("Creating LaTeX files")
594 tex_files
= generate_tex_files (settings
, lily_files
);
595 print ("Creating OrchestralLily package link")
596 generate_oly_link (settings
);
597 print ("Creating webshop_descriptions.def")
598 generate_webshop_files (settings
, lily_files
, tex_files
);
599 print ("Creating Makefile")
600 generate_make_files (settings
, lily_files
, tex_files
);