Commit d4a36739 authored by Reinhold Kainhofer's avatar Reinhold Kainhofer

Lilypond-book: Factor out the formatting from lilypond-book into separate classes

-) One base class BookOutputFormat for all output formats that contains most
   of the output logic
-) Each output format has its own BookOutputFormat-derived class with class-
   specific settings.
-) Each snippet type still has its own class, but calls appropriate hook
   functions of the format class to generate the actual output

This makes it much easier to add new formats in the future or use a
customized output format for special purposes (e.g. the MusicXML test
suite).

Also, got rid of the vars () idiom (which breaks when splitting up code
into separate functions/classes) and use a proper hash instead.
parent 4da9fc65
# -*- coding: utf-8 -*-
import lilylib as ly
import book_snippets as BookSnippet
from book_snippets import *
import re
global _;_=ly._
progress = ly.progress
warning = ly.warning
error = ly.error
########################################################################
# Helper functions
########################################################################
def find_file (name, include_path, raise_error=True):
for i in include_path:
full = os.path.join (i, name)
if os.path.exists (full):
return full
if raise_error:
error (_ ("file not found: %s") % name + '\n')
exit (1)
return ''
def verbatim_html (s):
return re.sub ('>', '>',
re.sub ('<', '&lt;',
re.sub ('&', '&amp;', s)))
########################################################################
# Option handling
########################################################################
#TODO: Definitions just once in all files!!!
LINE_WIDTH = 'line-width'
# TODO: Implement the intertext snippet option:
# 'intertext': r',?\s*intertext=\".*?\"',
default_snippet_opts = { 'alt': "[image of music]" }
########################################################################
# format handling
########################################################################
all_formats = []
def register_format (fmt):
all_formats.append (fmt)
########################################################################
# Snippet handling
########################################################################
# Use this for sorting the keys of the defined snippet types (the dict
# is unsorted, so we need to return sorted keys to ensure processing
# in a pre-defined order)
# Containing blocks must be first, see find_toplevel_snippets.
snippet_type_order = [
'multiline_comment',
'verbatim',
'verb',
'lilypond_block',
'singleline_comment',
'lilypond_file',
'include',
'lilypond',
'lilypondversion',
]
########################################################################
# Base class for all output formats
########################################################################
class BookOutputFormat:
def __init__ (self):
self.format = None
self.default_extension = None
self.snippet_res = {}
self.output = {}
self.handled_extensions = []
self.image_formats = "ps,png"
self.global_options = {}
self.document_language = ''
self.default_snippet_options = default_snippet_opts
self.snippet_option_separator = "\s*,\s*"
def supported_snippet_types (self):
# Sort according to snippet_type_order, unknown keys come last
keys = self.snippet_res.keys ()
# First the entries in snippet_type_order in that order (if present)
# then all entries not in snippet_type_order in given order
res = filter (lambda x:x in keys, snippet_type_order) + filter (lambda x:x not in snippet_type_order, keys)
return res
def snippet_regexp (self, snippettype):
return self.snippet_res.get (snippettype, None)
def can_handle_format (self, format):
return format == self.format
def can_handle_extension (self, extension):
return extension in self.handled_extensions
def add_options (self, option_parser):
pass
def process_options (self, global_options):
pass
def process_options_pdfnotdefault (self, global_options):
## prevent PDF from being switched on by default.
global_options.process_cmd += ' --formats=eps '
if global_options.create_pdf:
global_options.process_cmd += "--pdf -dinclude-eps-fonts -dgs-load-fonts "
if global_options.latex_program == 'latex':
global_options.latex_program = 'pdflatex'
def snippet_class (self, type):
return BookSnippet.snippet_type_to_class.get (type, BookSnippet.Snippet)
def get_document_language (self, source):
return ''
def init_default_snippet_options (self, source):
self.document_language = self.get_document_language (source)
if LINE_WIDTH not in self.default_snippet_options:
line_width = self.get_line_width (source)
if line_width:
self.default_snippet_options[LINE_WIDTH] = line_width
def get_line_width (self, source):
return None;
def split_snippet_options (self, option_string):
if option_string:
return re.split (self.snippet_option_separator, option_string)
return []
def input_fullname (self, input_filename):
return find_file (input_filename, self.global_options.include_path)
def adjust_snippet_command (self, cmd):
return cmd
def process_chunks (self, chunks):
return chunks
def snippet_output (self, basename, snippet):
warning (_("Output function not implemented"))
return ''
def output_simple (self, type, snippet):
return self.output.get (type, '') % snippet.get_replacements ()
def output_simple_replacements (self, type, variables):
return self.output.get (type, '') % variables
def output_print_filename (self, basename, snippet):
str = ''
rep = snippet.get_replacements ()
if PRINTFILENAME in snippet.option_dict:
rep['base'] = basename
rep['filename'] = os.path.basename (snippet.substring ('filename'))
str = self.output[PRINTFILENAME] % rep
return str
def required_files (self, snippet, base, full, required_files):
return []
def required_files_png (self, snippet, base, full, required_files):
# UGH - junk global_options
res = []
if (base + '.eps' in required_files and not snippet.global_options.skip_png_check):
page_count = BookSnippet.ps_page_count (full + '.eps')
if page_count <= 1:
res.append (base + '.png')
else:
for page in range (1, page_count + 1):
res.append (base + '-page%d.png' % page)
return res
# -*- coding: utf-8 -*-
import book_base as BookBase
from book_snippets import *
# Recognize special sequences in the input.
#
# (?P<name>regex) -- Assign result of REGEX to NAME.
# *? -- Match non-greedily.
# (?!...) -- Match if `...' doesn't match next (without consuming
# the string).
#
# (?m) -- Multiline regex: Make ^ and $ match at each line.
# (?s) -- Make the dot match all characters including newline.
# (?x) -- Ignore whitespace in patterns.
# Possible keys are:
# 'multiline_comment', 'verbatim', 'lilypond_block', 'singleline_comment',
# 'lilypond_file', 'include', 'lilypond', 'lilypondversion'
Docbook_snippet_res = {
'lilypond':
r'''(?smx)
(?P<match>
<(?P<inline>(inline)?)mediaobject>\s*
<textobject.*?>\s*
<programlisting\s+language="lilypond".*?(role="(?P<options>.*?)")?>
(?P<code>.*?)
</programlisting\s*>\s*
</textobject\s*>\s*
</(inline)?mediaobject>)''',
'lilypond_block':
r'''(?smx)
(?P<match>
<(?P<inline>(inline)?)mediaobject>\s*
<textobject.*?>\s*
<programlisting\s+language="lilypond".*?(role="(?P<options>.*?)")?>
(?P<code>.*?)
</programlisting\s*>\s*
</textobject\s*>\s*
</(inline)?mediaobject>)''',
'lilypond_file':
r'''(?smx)
(?P<match>
<(?P<inline>(inline)?)mediaobject>\s*
<imageobject.*?>\s*
<imagedata\s+
fileref="(?P<filename>.*?\.ly)"\s*
(role="(?P<options>.*?)")?\s*
(/>|>\s*</imagedata>)\s*
</imageobject>\s*
</(inline)?mediaobject>)''',
'multiline_comment':
r'''(?smx)
(?P<match>
\s*([email protected]\s+)
(?P<code><!--\s.*?!-->)
\s)''',
}
Docbook_output = {
FILTER: r'''<mediaobject>
<textobject>
<programlisting language="lilypond"
role="%(options)s">
%(code)s
</programlisting>
</textobject>
</mediaobject>''',
OUTPUT: r'''<imageobject role="latex">
<imagedata fileref="%(base)s.pdf" format="PDF"/>
</imageobject>
<imageobject role="html">
<imagedata fileref="%(base)s.png" format="PNG"/>
</imageobject>''',
PRINTFILENAME: r'''<textobject>
<simpara>
<ulink url="%(base)s.ly">
<filename>
%(filename)s
</filename>
</ulink>
</simpara>
</textobject>''',
VERBATIM: r'''<programlisting>
%(verb)s</programlisting>''',
VERSION: r'''%(program_version)s''',
}
class BookDocbookOutputFormat (BookBase.BookOutputFormat):
def __init__ (self):
BookBase.BookOutputFormat.__init__ (self)
self.format = "docbook"
self.default_extension = ".xml"
self.snippet_res = Docbook_snippet_res
self.output = Docbook_output
self.handled_extensions = ['.lyxml']
self.snippet_option_separator = '\s*'
def adjust_snippet_command (self, cmd):
if '--formats' not in cmd:
return cmd + ' --formats=png,pdf '
else:
return cmd
def snippet_output (self, basename, snippet):
str = ''
rep = snippet.get_replacements ();
for image in snippet.get_images ():
rep['image'] = image
(rep['base'], rep['ext']) = os.path.splitext (image)
str += self.output[OUTPUT] % rep
str += self.output_print_filename (basename, snippet)
if (snippet.substring('inline') == 'inline'):
str = '<inlinemediaobject>' + str + '</inlinemediaobject>'
else:
str = '<mediaobject>' + str + '</mediaobject>'
if VERBATIM in snippet.option_dict:
rep['verb'] = BookBase.verbatim_html (snippet.verb_ly ())
str = self.output[VERBATIM] % rep + str
return str
BookBase.register_format (BookDocbookOutputFormat ());
# -*- coding: utf-8 -*-
import book_base as BookBase
import copy
from book_snippets import *
# Recognize special sequences in the input.
#
# (?P<name>regex) -- Assign result of REGEX to NAME.
# *? -- Match non-greedily.
# (?!...) -- Match if `...' doesn't match next (without consuming
# the string).
#
# (?m) -- Multiline regex: Make ^ and $ match at each line.
# (?s) -- Make the dot match all characters including newline.
# (?x) -- Ignore whitespace in patterns.
# Possible keys are:
# 'multiline_comment', 'verbatim', 'lilypond_block', 'singleline_comment',
# 'lilypond_file', 'include', 'lilypond', 'lilypondversion'
HTML_snippet_res = {
'lilypond':
r'''(?mx)
(?P<match>
<lilypond(\s+(?P<options>.*?))?\s*:\s*(?P<code>.*?)\s*/>)''',
'lilypond_block':
r'''(?msx)
(?P<match>
<lilypond\s*(?P<options>.*?)\s*>
(?P<code>.*?)
</lilypond\s*>)''',
'lilypond_file':
r'''(?mx)
(?P<match>
<lilypondfile\s*(?P<options>.*?)\s*>
\s*(?P<filename>.*?)\s*
</lilypondfile\s*>)''',
'multiline_comment':
r'''(?smx)(?P<match>\s*([email protected]\s+)(?P<code><!--\s.*?!-->)\s)''',
'verb':
r'''(?x)(?P<match>(?P<code><pre>.*?</pre>))''',
'verbatim':
r'''(?xs)(?P<match>(?P<code><pre>\s.*?</pre>\s))''',
'lilypondversion':
r'''(?mx)(?P<match><lilypondversion\s*/>)''',
}
HTML_output = {
FILTER: r'''<lilypond %(options)s>
%(code)s
</lilypond>
''',
AFTER: r'''
</a>
</p>''',
BEFORE: r'''<p>
<a href="%(base)s.ly">''',
OUTPUT: r'''
<img align="middle"
border="0"
src="%(image)s"
alt="%(alt)s">''',
PRINTFILENAME: '<p><tt><a href="%(base)s.ly">%(filename)s</a></tt></p>',
QUOTE: r'''<blockquote>
%(str)s
</blockquote>
''',
VERBATIM: r'''<pre>
%(verb)s</pre>''',
VERSION: r'''%(program_version)s''',
}
class BookHTMLOutputFormat (BookBase.BookOutputFormat):
def __init__ (self):
BookBase.BookOutputFormat.__init__ (self)
self.format = "html"
self.default_extension = ".html"
self.snippet_res = HTML_snippet_res
self.output = HTML_output
self.handled_extensions = ['.html', '.xml','.htmly']
self.snippet_option_separator = '\s*'
def split_snippet_options (self, option_string):
if option_string:
options = re.findall('[\w\.-:]+(?:\s*=\s*(?:"[^"]*"|\'[^\']*\'|\S+))?',
option_string)
options = [re.sub('^([^=]+=\s*)(?P<q>["\'])(.*)(?P=q)', '\g<1>\g<3>', opt)
for opt in options]
return options
return []
def adjust_snippet_command (self, cmd):
if '--formats' not in cmd:
return cmd + ' --formats=png '
else:
return cmd
def snippet_output (self, basename, snippet):
str = ''
rep = snippet.get_replacements ();
rep['base'] = basename
str += self.output_print_filename (basename, snippet)
if VERBATIM in snippet.option_dict:
rep['verb'] = BookBase.verbatim_html (snippet.verb_ly ())
str += self.output[VERBATIM] % rep
if QUOTE in snippet.option_dict:
str = self.output[QUOTE] % rep
str += self.output[BEFORE] % rep
for image in snippet.get_images ():
rep1 = copy.copy (rep)
rep1['image'] = image
(rep1['base'], rep1['ext']) = os.path.splitext (image)
rep1['alt'] = snippet.option_dict[ALT]
str += self.output[OUTPUT] % rep1
str += self.output[AFTER] % rep
return str
def required_files (self, snippet, base, full, required_files):
return self.required_files_png (snippet, base, full, required_files)
BookBase.register_format (BookHTMLOutputFormat ());
# -*- coding: utf-8 -*-
import re
import tempfile
import os
import book_base as BookBase
from book_snippets import *
import lilylib as ly
# Recognize special sequences in the input.
#
# (?P<name>regex) -- Assign result of REGEX to NAME.
# *? -- Match non-greedily.
# (?!...) -- Match if `...' doesn't match next (without consuming
# the string).
#
# (?m) -- Multiline regex: Make ^ and $ match at each line.
# (?s) -- Make the dot match all characters including newline.
# (?x) -- Ignore whitespace in patterns.
# Possible keys are:
# 'multiline_comment', 'verbatim', 'lilypond_block', 'singleline_comment',
# 'lilypond_file', 'include', 'lilypond', 'lilypondversion'
Latex_snippet_res = {
'include':
r'''(?smx)
^[^%\n]*?
(?P<match>
\\input\s*{
(?P<filename>\S+?)
})''',
'lilypond':
r'''(?smx)
^[^%\n]*?
(?P<match>
\\lilypond\s*(
\[
\s*(?P<options>.*?)\s*
\])?\s*{
(?P<code>.*?)
})''',
'lilypond_block':
r'''(?smx)
^[^%\n]*?
(?P<match>
\\begin\s*(?P<env>{lilypond}\s*)?(
\[
\s*(?P<options>.*?)\s*
\])?(?(env)|\s*{lilypond})
(?P<code>.*?)
^[^%\n]*?
\\end\s*{lilypond})''',
'lilypond_file':
r'''(?smx)
^[^%\n]*?
(?P<match>
\\lilypondfile\s*(
\[
\s*(?P<options>.*?)\s*
\])?\s*\{
(?P<filename>\S+?)
})''',
'singleline_comment':
r'''(?mx)
^.*?
(?P<match>
(?P<code>
%.*$\n+))''',
'verb':
r'''(?mx)
^[^%\n]*?
(?P<match>
(?P<code>
\\verb(?P<del>.)
.*?
(?P=del)))''',
'verbatim':
r'''(?msx)
^[^%\n]*?
(?P<match>
(?P<code>
\\begin\s*{verbatim}
.*?
\\end\s*{verbatim}))''',
'lilypondversion':
r'''(?smx)
(?P<match>
\\lilypondversion)[^a-zA-Z]''',
}
Latex_output = {
FILTER: r'''\begin{lilypond}[%(options)s]
%(code)s
\end{lilypond}''',
OUTPUT: r'''{%%
\parindent 0pt
\ifx\preLilyPondExample \undefined
\else
\expandafter\preLilyPondExample
\fi
\def\lilypondbook{}%%
\input %(base)s-systems.tex
\ifx\postLilyPondExample \undefined
\else
\expandafter\postLilyPondExample
\fi
}''',
PRINTFILENAME: '''\\texttt{%(filename)s}
''',
QUOTE: r'''\begin{quotation}
%(str)s
\end{quotation}''',
VERBATIM: r'''\noindent
\begin{verbatim}%(verb)s\end{verbatim}
''',
VERSION: r'''%(program_version)s''',
}
###
# Retrieve dimensions from LaTeX
LATEX_INSPECTION_DOCUMENT = r'''
\nonstopmode
%(preamble)s
\begin{document}
\typeout{textwidth=\the\textwidth}
\typeout{columnsep=\the\columnsep}
\makeatletter\[email protected]\typeout{columns=2}\fi\makeatother
\end{document}
'''
# Do we need anything else besides `textwidth'?
def get_latex_textwidth (source, global_options):
m = re.search (r'''(?P<preamble>\\begin\s*{document})''', source)
if m == None:
warning (_ ("cannot find \\begin{document} in LaTeX document"))