Spaces:
Sleeping
Sleeping
| # book_base.py | |
| # -*- coding: utf-8 -*- | |
| # | |
| # This file is part of LilyPond, the GNU music typesetter. | |
| # | |
| # Copyright (C) 2010 Reinhold Kainhofer <reinhold@kainhofer.com>, | |
| # 2020--2020 Han-Wen Nienhuys <hanwen@xs4all.nl> | |
| # | |
| # LilyPond is free software: you can redistribute it and/or modify | |
| # it under the terms of the GNU General Public License as published by | |
| # the Free Software Foundation, either version 3 of the License, or | |
| # (at your option) any later version. | |
| # | |
| # LilyPond is distributed in the hope that it will be useful, | |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| # GNU General Public License for more details. | |
| # | |
| # You should have received a copy of the GNU General Public License | |
| # along with LilyPond. If not, see <http://www.gnu.org/licenses/>. | |
| import os | |
| import re | |
| import sys | |
| import book_snippets as BookSnippet | |
| import book_snippets | |
| import lilylib as ly | |
| progress = ly.progress | |
| warning = ly.warning | |
| error = ly.error | |
| ######################################################################## | |
| # Helper functions | |
| ######################################################################## | |
| def find_file(name, include_path, working_dir, raise_error=True): | |
| assert working_dir | |
| current_path = working_dir | |
| for i in [current_path] + include_path: | |
| full = os.path.join(i, name) | |
| full = os.path.normpath(os.path.join(current_path, full)) | |
| if os.path.exists(full): | |
| return full | |
| if raise_error: | |
| error(_("file not found: %s") % name + '\n') | |
| sys.exit(1) | |
| return '' | |
| def verbatim_html(s): | |
| return re.sub('>', '>', | |
| re.sub('<', '<', | |
| re.sub('&', '&', 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 | |
| # snippet_type string => regex string | |
| # Possible keys are: | |
| # 'multiline_comment', 'verbatim', 'lilypond_block', 'singleline_comment', | |
| # 'lilypond_file', 'include', 'lilypond', 'lilypondversion' | |
| # 'musicxml_file', | |
| 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 = r"\s*,\s*" | |
| def supported_snippet_types(self): | |
| """List of snippet types (strings)""" | |
| # Sort according to snippet_type_order, unknown keys come last | |
| keys = list(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 = [x for x in snippet_type_order if x in keys] + \ | |
| [x for x in keys if x not in snippet_type_order] | |
| return res | |
| def snippet_regexp(self, snippettype): | |
| """return regex string for 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, | |
| self.global_options.original_dir) | |
| 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): | |
| s = '' | |
| rep = snippet.get_replacements() | |
| if book_snippets.PRINTFILENAME in snippet.option_dict: | |
| rep['base'] = basename | |
| rep['filename'] = os.path.basename(snippet.filename) | |
| rep['ext'] = snippet.ext | |
| s = self.output[book_snippets.PRINTFILENAME] % rep | |
| return s | |
| 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 | |
| def find_linestarts(s): | |
| """Return a list of indices indicating the first char of a line.""" | |
| nls = [0] | |
| start = 0 | |
| end = len(s) | |
| while True: | |
| i = s.find('\n', start) | |
| if i < 0: | |
| break | |
| i = i + 1 | |
| nls.append(i) | |
| start = i | |
| nls.append(len(s)) | |
| return nls | |
| def find_toplevel_snippets(input_string, formatter, global_options): | |
| res = {} | |
| types = formatter.supported_snippet_types() | |
| for t in types: | |
| res[t] = re.compile(formatter.snippet_regexp(t)) | |
| snippets = [] | |
| index = 0 | |
| found = dict([(t, None) for t in types]) | |
| line_starts = find_linestarts(input_string) | |
| line_start_idx = 0 | |
| # We want to search for multiple regexes, without searching | |
| # the string multiple times for one regex. | |
| # Hence, we use earlier results to limit the string portion | |
| # where we search. | |
| # Since every part of the string is traversed at most once for | |
| # every type of snippet, this is linear. | |
| while True: | |
| first = None | |
| endex = 1 << 30 | |
| for type in types: | |
| if not found[type] or found[type][0] < index: | |
| found[type] = None | |
| m = res[type].search(input_string, index, endex) | |
| if not m: | |
| continue | |
| klass = formatter.snippet_class(type) | |
| start = m.start('match') | |
| line_number = line_start_idx | |
| while line_starts[line_number] < start: | |
| line_number += 1 | |
| line_number += 1 | |
| snip = klass(type, m, formatter, line_number, global_options) | |
| found[type] = (start, snip) | |
| if (found[type] | |
| and (not first | |
| or found[type][0] < found[first][0])): | |
| first = type | |
| # FIXME. | |
| # Limiting the search space is a cute | |
| # idea, but this *requires* to search | |
| # for possible containing blocks | |
| # first, at least as long as we do not | |
| # search for the start of blocks, but | |
| # always/directly for the entire | |
| # @block ... @end block. | |
| endex = found[first][0] | |
| if not first: | |
| snippets.append(BookSnippet.Substring( | |
| input_string, index, len(input_string), line_start_idx)) | |
| break | |
| while start > line_starts[line_start_idx+1]: | |
| line_start_idx += 1 | |
| (start, snip) = found[first] | |
| snippets.append(BookSnippet.Substring( | |
| input_string, index, start, line_start_idx + 1)) | |
| snippets.append(snip) | |
| found[first] = None | |
| index = start + len(snip.match.group('match')) | |
| return snippets | |