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') | |