Spaces:
Sleeping
Sleeping
| #!@PYTHON@ | |
| # | |
| # This file is part of LilyPond, the GNU music typesetter. | |
| # | |
| # Copyright (C) 2006--2020 John Mandereau <john.mandereau@gmail.com> | |
| # | |
| # 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/>. | |
| """ | |
| Postprocess HTML files: | |
| add footer, tweak links, add language selection menu. | |
| """ | |
| import codecs | |
| import re | |
| import os | |
| import sys | |
| import time | |
| import langdefs | |
| def _doc(s): | |
| return s | |
| program_name = os.path.basename(sys.argv[0]) | |
| footer = ''' | |
| <p class="footer_version"> | |
| %(footer_name_version)s | |
| </p> | |
| <p class="footer_report"> | |
| %(footer_report_links)s | |
| </p> | |
| ''' | |
| footer_name_version = _doc('This page is for %(package_name)s-' | |
| '%(package_version)s (%(branch_str)s).') | |
| # ugh, must not have "_doc" in strings because it is naively replaced with "_" | |
| # in hacked gettext process | |
| footer_report_links = _doc('We welcome your aid; please ' | |
| '<a href="%(help_us_url)s">help us</a> by ' | |
| 'reporting errors to our ' | |
| '<a href="%(bug_lilypond_url)s">bug list</a>.') | |
| sidebar_version = _doc(' v%(package_version)s (%(branch_str)s).') | |
| bug_lilypond_url = 'https://lists.gnu.org/mailman/listinfo/bug-lilypond' | |
| help_us_url = 'https://lilypond.org/help-us.html' | |
| header_tag = '<!-- header_tag -->' | |
| header_tag_re = re.compile(header_tag) | |
| lang_available = _doc("Other languages: %s.") | |
| browser_lang = _doc('About <a href="%s">automatic language selection</a>.') | |
| browser_language_url = "http://www.lilypond.org/website/misc/browser-language" | |
| LANGUAGES_TEMPLATE = ''' | |
| <p id="languages"> | |
| %(language_available)s | |
| <br> | |
| %(browser_language)s | |
| </p> | |
| ''' | |
| html_re = re.compile('(.*?)(?:[.]([^/.]*))?[.]html$') | |
| def build_pages_dict(filelist): | |
| """Build dictionary of available translations of each page. | |
| Returns: basename => list of languages dict""" | |
| pages_dict = {} | |
| language_codes = set([l.webext for l in langdefs.LANGUAGES]) | |
| for f in filelist: | |
| m = html_re.match(f) | |
| if m: | |
| g = m.groups() | |
| if len(g) <= 1 or g[1] is None: | |
| e = '' | |
| else: | |
| e = g[1] | |
| if e not in language_codes: | |
| continue | |
| if not g[0] in pages_dict: | |
| pages_dict[g[0]] = [e] | |
| else: | |
| pages_dict[g[0]].append(e) | |
| return pages_dict | |
| body_tag_re = re.compile('(?i)<body([^>]*)>') | |
| html_tag_re = re.compile('(?i)<html>') | |
| doctype_re = re.compile('(?i)<!DOCTYPE') | |
| doctype = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">\n' | |
| css_re = re.compile(r'(?i)<link ([^>]*)href="[^">]*?' | |
| r'(lilypond.*\.css)"([^>]*)>') | |
| end_head_tag_re = re.compile('(?i)</head>') | |
| css_link = (' <link rel="stylesheet" type="text/css" title="Default design"' | |
| ' href="%(rel)sDocumentation/css/lilypond-manuals.css">\n' | |
| ' <!--[if lte IE 7]>\n' | |
| ' <link href="%(rel)sDocumentation/css/lilypond-ie-fixes.css"' | |
| ' rel="stylesheet" type="text/css">\n' | |
| ' <![endif]-->\n') | |
| def add_header(s, prefix): | |
| """Add header (<body>, doctype and CSS)""" | |
| header = "" | |
| if header_tag_re.search(s) == None: | |
| body = '<body\\1>' | |
| (s, n) = body_tag_re.subn(body + header, s, 1) | |
| if not n: | |
| (s, n) = html_tag_re.subn('<html>' + header, s, 1) | |
| if not n: | |
| s = header + s | |
| if doctype_re.search(s) is None: | |
| s = doctype + header_tag + '\n' + s | |
| if css_re.search(s) is None: | |
| depth = (prefix.count('/') - 1) * '../' | |
| s = end_head_tag_re.sub((css_link % {'rel': depth}) | |
| + '</head>', s) | |
| return s | |
| footer_insert_re = re.compile(r'<!--\s*FOOTER\s*-->') | |
| end_body_re = re.compile(r'(?i)</body>') | |
| verifier_div = '<div id="verifier_texinfo">' | |
| verifier_re = re.compile(verifier_div) | |
| def add_footer(s, footer_text): | |
| """add footer""" | |
| # prefer inserting at FOOTER; this is necessary for the footer to | |
| # be positioned correctly relative to the nav frame in the manuals. | |
| (s, n) = footer_insert_re.subn(footer_text + '\n' + '<!-- FOOTER -->', | |
| s, 1) | |
| if not n: | |
| (s, n) = verifier_re.subn(footer_text + '\n' + verifier_div, s, 1) | |
| if not n: | |
| (s, n) = end_body_re.subn(footer_text + '\n' + '</body>', s, 1) | |
| if not n: | |
| # this happens with the HTML files under Documentation/misc | |
| s += footer_text | |
| return s | |
| def find_translations(pages_dict, prefix, lang_ext): | |
| """find available translations of a page""" | |
| available = [] | |
| for l in langdefs.LANGUAGES: | |
| e = l.webext | |
| if lang_ext != e: | |
| if e in pages_dict[prefix]: | |
| available.append(l) | |
| return available | |
| online_links_re = re.compile('''(href|src)=['"]\ | |
| ((?!Compiling-from-source.html")[^/][.]*[^.:'"]*)\ | |
| ([.]html)(#[^"']*|)['"]''') | |
| offline_links_re = re.compile('''href=['"]\ | |
| ((?!Compiling-from-source.html")(?![.]{2}/contributor)[^/][.]*[^.:'"]*)\ | |
| ([.]html)(#[^"\']*|)[\'"]''') | |
| big_page_name_re = re.compile('''(.+?)-big-page''') | |
| def process_i18n_big_page_links(pages_dict, match, prefix, lang_ext): | |
| big_page_name = big_page_name_re.match(match.group(1)) | |
| if big_page_name: | |
| destination_path = os.path.normpath(os.path.join( | |
| os.path.dirname(prefix), | |
| big_page_name.group(0))) | |
| if not (destination_path in pages_dict and | |
| lang_ext in pages_dict[destination_path]): | |
| return match.group(0) | |
| return ('href="' + match.group(1) + '.' + lang_ext | |
| + match.group(2) + match.group(3) + '"') | |
| def process_links(pages_dict, content, prefix, lang_ext, file_name, target): | |
| page_flavors = {} | |
| if target == 'online': | |
| # Strip .html, suffix for auto language selection (content | |
| # negotiation). The menu must keep the full extension, so do | |
| # this before adding the menu. | |
| page_flavors[file_name] = [lang_ext, | |
| online_links_re.sub('\\1="\\2\\4"', content)] | |
| elif target == 'offline': | |
| # in LANG doc index: don't rewrite .html suffixes | |
| # as not all .LANG.html pages exist; | |
| # the doc index should be translated and contain links | |
| # with the right suffixes | |
| # idem for NEWS | |
| if lang_ext == '': | |
| page_flavors[file_name] = [lang_ext, content] | |
| else: | |
| # For saving bandwidth and disk space, we don't duplicate big pages | |
| # in English, so we must process translated | |
| # big pages links differently. | |
| if 'big-page' in prefix: | |
| page_flavors[file_name] = [lang_ext, | |
| offline_links_re.sub( | |
| lambda match: | |
| process_i18n_big_page_links( | |
| pages_dict, | |
| match, prefix, lang_ext), | |
| content)] | |
| else: | |
| page_flavors[file_name] = [lang_ext, | |
| offline_links_re.sub( | |
| 'href="\\1.' + lang_ext | |
| + '\\2\\3"', content)] | |
| return page_flavors | |
| def add_menu(page_flavors, prefix, available, target, translation): | |
| for k in page_flavors: | |
| language_menu = '' | |
| if page_flavors[k][0] != '': | |
| t = translation[page_flavors[k][0]] | |
| else: | |
| t = _doc | |
| for lang in available: | |
| lang_file = lang.file_name(os.path.basename(prefix), '.html') | |
| if language_menu != '': | |
| language_menu += ', ' | |
| language_menu += '<a href="%s">%s</a>' % (lang_file, t(lang.name)) | |
| languages = '' | |
| if language_menu: | |
| browser_language = t(browser_lang) % browser_language_url | |
| language_available = t(lang_available) % language_menu | |
| languages = LANGUAGES_TEMPLATE % vars() | |
| full_footer = '' | |
| if 'web' not in prefix: | |
| full_footer += footer | |
| full_footer += languages | |
| full_footer = '''<div id="footer">%s</div>''' % full_footer | |
| page_flavors[k][1] = add_footer(page_flavors[k][1], full_footer) | |
| return page_flavors | |
| def process_html_files(pages_dict, | |
| package_name='', | |
| package_version='', | |
| target='offline'): | |
| """Add header, footer and tweak links to a number of HTML files | |
| Arguments: | |
| pages_dict: dict of filename => translations | |
| package_name=NAME set package_name to NAME | |
| package_version=VERSION set package version to VERSION | |
| targets=offline|online set page processing depending on the target | |
| offline is for reading HTML pages locally | |
| online is for hosting the HTML pages on a website with content | |
| negotiation | |
| """ | |
| versiontup = package_version.split('.') | |
| branch_str = _doc('stable-branch') | |
| if int(versiontup[1]) % 2: | |
| branch_str = _doc('development-branch') | |
| en_dict = { | |
| 'package_name': package_name, | |
| 'package_version': package_version, | |
| 'branch_str': branch_str, | |
| 'help_us_url': 'https://lilypond.org/help-us.html', | |
| 'bug_lilypond_url': 'https://lists.gnu.org/mailman/listinfo/bug-lilypond', | |
| 'footer_name_version': _doc ('This page is for %(package_name)s-' | |
| '%(package_version)s (%(branch_str)s).'), | |
| 'footer_report_links': _doc ('We welcome your aid; please ' | |
| '<a href="%(help_us_url)s">help us</a> by ' | |
| 'reporting errors to our ' | |
| '<a href="%(bug_lilypond_url)s">bug list</a>.'), | |
| } | |
| # language => (dict of str => str) | |
| subst = { | |
| '': en_dict, | |
| } | |
| for l in langdefs.translation: | |
| e = langdefs.LANGDICT[l].webext | |
| if e: | |
| subst[e] = { | |
| name: langdefs.translation[l](en_dict[name]) | |
| for name in en_dict} | |
| # Do deeper string formatting as early as possible, | |
| # so only one '%' formatting pass is needed later | |
| for e in subst: | |
| for k in ['footer_name_version', 'footer_report_links']: | |
| subst[e][k] = subst[e][k] % subst[e] | |
| for prefix, ext_list in list(pages_dict.items()): | |
| for lang_ext in ext_list: | |
| file_name = langdefs.lang_file_name(prefix, lang_ext, '.html') | |
| dest_time = 0 | |
| content = codecs.open(file_name, 'r', 'utf-8').read() | |
| content = content.replace('%', '%%') | |
| content = add_header(content, prefix) | |
| # add sidebar information | |
| content = content.replace('<!-- Sidebar Version Tag -->', sidebar_version) | |
| available = find_translations(pages_dict, prefix, lang_ext) | |
| page_flavors = process_links(pages_dict, | |
| content, prefix, lang_ext, file_name, target) | |
| # Add menu after stripping: must not have autoselection for language menu. | |
| page_flavors = add_menu( | |
| page_flavors, prefix, available, target, langdefs.translation) | |
| for k in page_flavors: | |
| page_flavors[k][1] = page_flavors[k][1] % subst[page_flavors[k][0]] | |
| # Must write to tmp file to avoid touching hardlinked files. | |
| out_f = codecs.open(k + ".tmp", 'w', 'utf-8') | |
| out_f.write(page_flavors[k][1]) | |
| out_f.close() | |
| os.rename(k + ".tmp", k) | |
| # if the page is translated, a .en.html symlink is necessary for content negotiation | |
| if target == 'online' and ext_list != [''] and not os.path.lexists(prefix + '.en.html'): | |
| os.symlink(os.path.basename(prefix) + '.html', prefix + '.en.html') | |