]> git.donarmstrong.com Git - lilypond.git/blob - scripts/build/extract_texi_filenames.py
WEB: Integration of new web site [general.texi].
[lilypond.git] / scripts / build / extract_texi_filenames.py
1 #!@PYTHON@
2 # -*- coding: utf-8 -*-
3 # extract_texi_filenames.py
4
5 # USAGE:  extract_texi_filenames.py [-o OUTDIR] FILES
6 #
7 # -o OUTDIR specifies that output files should rather be written in OUTDIR
8 #
9 # Description:
10 # This script parses the .texi file given and creates a file with the
11 # nodename <=> filename/anchor map.
12 # The idea behind: Unnumbered subsections go into the same file as the
13 # previous numbered section, @translationof gives the original node name,
14 # which is then used for the filename/anchor.
15 #
16 # If this script is run on a file texifile.texi, it produces a file
17 # texifile[.LANG].xref-map with tab-separated entries of the form
18 #        NODE\tFILENAME\tANCHOR
19 # LANG is the document language in case it's not 'en'
20 # Note: The filename does not have any extension appended!
21 # This file can then be used by our texi2html init script to determine
22 # the correct file name and anchor for external refs
23
24 import sys
25 import re
26 import os
27 import getopt
28
29 options_list, files = getopt.getopt (sys.argv[1:],'o:s:hI:m:',
30                                      ['output=', 'split=',
31                                       'help', 'include=',
32                                       'master-map-file='])
33
34 help_text = r"""Usage: %(program_name)s [OPTIONS]... TEXIFILE...
35 Extract files names for texinfo (sub)sections from the texinfo files.
36
37 Options:
38  -h, --help                     print this help
39  -I, --include=DIRECTORY        append DIRECTORY to include search path
40  -m, --master-map-file=FILE     use FILE as master map file
41  -o, --output=DIRECTORY         write .xref-map files to DIRECTORY
42  -s, --split=MODE               split manual according to MODE. Possible values
43                                 are section and custom (default)
44 """
45
46 def help (text):
47     sys.stdout.write ( text)
48     sys.exit (0)
49
50 outdir = '.'
51 split = "custom"
52 include_path = []
53 master_map_file = ''
54 initial_map = {}
55 for opt in options_list:
56     o = opt[0]
57     a = opt[1]
58     if o == '-h' or o == '--help':
59         help (help_text % vars ())
60     if o == '-I' or o == '--include':
61         if os.path.isdir (a):
62             include_path.append (a)
63         else:
64             print 'NOT A DIR from: ', os.getcwd (), a
65     elif o == '-o' or o == '--output':
66         outdir = a
67     elif o == '-s' or o == '--split':
68         split = a
69     elif o == '-m' or o == '--master-map-file':
70         if os.path.isfile (a):
71             master_map_file = a
72     else:
73         raise Exception ('unknown option: ' + o)
74
75
76 if not os.path.isdir (outdir):
77     if os.path.exists (outdir):
78         os.unlink (outdir)
79     os.makedirs (outdir)
80
81 include_re = re.compile (r'@include ((?!../lily-).*?\.i?texi)$', re.M)
82 whitespaces = re.compile (r'\s+')
83 section_translation_re = re.compile ('^@(node|(?:unnumbered|appendix)\
84 (?:(?:sub){0,2}sec)?|top|chapter|(?:sub){0,2}section|\
85 (?:major|chap|(?:sub){0,2})heading|lydoctitle|translationof) \
86 (.+)$', re.MULTILINE)
87 external_node_re = re.compile (r'\s+@c\s+external.*')
88
89 def expand_includes (m, filename):
90     filepath = os.path.join (os.path.dirname (filename), m.group(1))
91     if os.path.exists (filepath):
92         return extract_sections (filepath)[1]
93     else:
94         for directory in include_path:
95             filepath = os.path.join (directory, m.group(1))
96             if os.path.exists (filepath):
97                 return extract_sections (filepath)[1]
98         print 'No such file: ' + filepath
99         return ''
100
101 lang_re = re.compile (r'^@documentlanguage (.+)', re.M)
102
103 def extract_sections (filename):
104     result = ''
105     f = open (filename, 'r')
106     page = f.read ()
107     f.close()
108     # Search document language
109     m = lang_re.search (page)
110     if m and m.group (1) != 'en':
111         lang_suffix = '.' + m.group (1)
112     else:
113         lang_suffix = ''
114     # Replace all includes by their list of sections and extract all sections
115     page = include_re.sub (lambda m: expand_includes (m, filename), page)
116     sections = section_translation_re.findall (page)
117     for sec in sections:
118         result += "@" + sec[0] + " " + sec[1] + "\n"
119     return (lang_suffix, result)
120
121 # Convert a given node name to its proper file name (normalization as
122 # explained in the texinfo manual:
123 # http://www.gnu.org/software/texinfo/manual/texinfo/html_node/HTML-Xref-Node-Name-Expansion.html
124 def texinfo_file_name(title):
125     # exception: The top node is always mapped to index.html
126     if title == "Top":
127         return "index"
128     # File name normalization by texinfo (described in the texinfo manual):
129     # 1/2: letters and numbers are left unchanged
130     # 3/4: multiple, leading and trailing whitespace is removed
131     title = title.strip ();
132     title = whitespaces.sub (' ', title)
133     # 5:   all remaining spaces are converted to '-'
134     # 6:   all other 7- or 8-bit chars are replaced by _xxxx (xxxx=ascii character code)
135     result = ''
136     for index in range(len(title)):
137         char = title[index]
138         if char == ' ': # space -> '-'
139             result += '-'
140         elif ( ('0' <= char and char <= '9' ) or
141                ('A' <= char and char <= 'Z' ) or
142                ('a' <= char and char <= 'z' ) ):  # number or letter
143             result += char
144         else:
145             ccode = ord(char)
146             if ccode <= 0xFFFF:
147                 result += "_%04x" % ccode
148             else:
149                 result += "__%06x" % ccode
150     # 7: if name begins with number, prepend 't_g' (so it starts with a letter)
151     if (result != '') and (ord(result[0]) in range (ord('0'), ord('9'))):
152         result = 't_g' + result
153     return result
154
155 texinfo_re = re.compile (r'@.*{(.*)}')
156 def remove_texinfo (title):
157     return texinfo_re.sub (r'\1', title)
158
159 def create_texinfo_anchor (title):
160     return texinfo_file_name (remove_texinfo (title))
161
162 unnumbered_re = re.compile (r'unnumbered.+|lydoctitle')
163 file_name_section_level = {
164     'top': 4,
165     'chapter':3,
166     'unnumbered':3,
167     'appendix':3,
168     'section':2,
169     'unnumberedsec':2,
170     'appendixsec':2,
171     'subsection':1,
172     'unnumberedsubsec':1,
173     'appendixsubsec':1,
174     'subsubsection':0,
175     'unnumberedsubsubsec':0,
176     'appendixsubsubsec':0
177 }
178 if split in file_name_section_level:
179     splitting_level = file_name_section_level[split]
180 else:
181     splitting_level = -1
182 def process_sections (filename, lang_suffix, page):
183     sections = section_translation_re.findall (page)
184     basename = os.path.splitext (os.path.basename (filename))[0]
185     p = os.path.join (outdir, basename) + lang_suffix + '.xref-map'
186     f = open (p, 'w')
187
188     this_title = ''
189     this_filename = 'index'
190     this_anchor = ''
191     this_unnumbered = False
192     had_section = False
193     for sec in sections:
194         if sec[0] == "node":
195             # Write out the cached values to the file and start a new
196             # section:
197             if this_title and this_title != 'Top':
198                     f.write (this_title + "\t" + this_filename + "\t" + this_anchor + "\n")
199             had_section = False
200             this_title = remove_texinfo (sec[1])
201             this_anchor = create_texinfo_anchor (sec[1])
202             # delete entry from master map file
203             if this_title in initial_map:
204                 del initial_map[this_title]
205         elif sec[0] == "translationof":
206             (original_node, external_node) = external_node_re.subn ('', sec[1])
207             original_node = remove_texinfo (original_node)
208             # The following binds the translator to use the
209             # translated node name in cross-references in case
210             # it exists
211             if external_node and original_node in initial_map:
212                 del initial_map[original_node]
213             anchor = create_texinfo_anchor (sec[1])
214             # If @translationof is used, it gives the original
215             # node name, which we use for the anchor and the file
216             # name (if it is a numbered node)
217             this_anchor = anchor
218             if not this_unnumbered:
219                 this_filename = anchor
220             elif original_node in initial_map:
221                 this_filename = initial_map[original_node][2]
222         else:
223             # Some pages might not use a node for every section, so
224             # treat this case here, too: If we already had a section
225             # and encounter another one before the next @node, we
226             # write out the old one and start with the new values
227             if had_section and this_title:
228                 f.write (this_title + "\t" + this_filename + "\t" + this_anchor + "\n")
229                 this_title = remove_texinfo (sec[1])
230                 this_anchor = create_texinfo_anchor (sec[1])
231             had_section = True
232
233             if split == 'custom':
234                 # unnumbered nodes use the previously used file name,
235                 # only numbered nodes get their own filename! However,
236                 # top-level @unnumbered still get their own file.
237                 this_unnumbered = unnumbered_re.match (sec[0])
238                 if not this_unnumbered:
239                     this_filename = this_anchor
240             elif split == 'node':
241                 this_filename = this_anchor
242             else:
243                 if sec[0] in file_name_section_level and \
244                         file_name_section_level[sec[0]] >= splitting_level:
245                     this_filename = this_anchor
246
247     if this_title and this_title != 'Top':
248         f.write (this_title + "\t" + this_filename + "\t" + this_anchor + "\n")
249
250     for node in initial_map:
251         f.write ("\t".join (initial_map[node]) + "\n")
252     f.close ()
253
254 xref_map_line_re = re.compile (r'(.*?)\t(.*?)\t(.*?)$')
255 if master_map_file:
256     for line in open (master_map_file):
257         m = xref_map_line_re.match (line)
258         if m:
259             initial_map[m.group (1)] = (m.group (1), m.group (2), m.group (3))
260
261 for filename in files:
262     print "extract_texi_filenames.py: Processing %s" % filename
263     (lang_suffix, sections) = extract_sections (filename)
264     process_sections (filename, lang_suffix, sections)