mkhtml2: planning comments update
[gtk-doc.git] / gtkdoc / rebase.py
blob75713ce53804c76428ca3698ead8e635d3ef07a8
1 # -*- python -*-
3 # gtk-doc - GTK DocBook documentation generator.
4 # Copyright (C) 1998 Damon Chaplin
5 # 2007 David Necas (Yeti)
6 # 2007-2016 Stefan Sauer
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 """
24 The rebase tool rewrites URI references in installed HTML documentation.
25 """
27 from __future__ import print_function
28 from six import iteritems, iterkeys
30 import logging
31 import os
32 import re
33 import subprocess
35 from . import common
37 # Maps.
38 # These two point to the last seen URI of given type for a package:
39 # OnlineMap: package => on-line URI
40 # LocalMap: package => local URI
41 # This maps all seen URIs of a package to fix broken links in the process:
42 # RevMap: URI => package
43 OnlineMap = {}
44 LocalMap = {}
45 RevMap = {}
46 # Remember what mangling we did.
47 Mapped = {}
50 def log(options, *msg):
51 if options.verbose:
52 print(*msg)
55 def run(options):
56 other_dirs = []
58 # We scan the directory containing GLib and any directories in GNOME2_PATH
59 # first, but these will be overriden by any later scans.
60 if "GNOME2_PATH" in os.environ:
61 for dir in os.environ["GNOME2_PATH"].split(':'):
62 dir = os.path.join(dir, "share/gtk-doc/html")
63 if os.path.isdir(dir):
64 log(options, "Prepending GNOME2_PATH directory:", dir)
65 other_dirs = [dir] + other_dirs
67 glib_dir = common.GetModuleDocDir('glib-2.0')
68 if glib_dir:
69 log(options, "Prepending GLib directory", glib_dir)
70 other_dirs = [glib_dir] + other_dirs
72 # Check all other dirs, but skip already scanned dirs ord subdirs of those
74 for dir in other_dirs:
75 ScanDirectory(dir, options)
77 if options.relative:
78 RelativizeLocalMap(options.html_dir, options)
80 RebaseReferences(options.html_dir, options)
81 PrintWhatWeHaveDone()
84 def ScanDirectory(scan_dir, options):
85 log(options, "Scanning documentation directory %s", scan_dir)
87 if scan_dir == options.html_dir:
88 log(options, "Excluding self")
89 return
91 if not os.path.isdir(scan_dir):
92 logging.info('Cannot open dir "%s"', scan_dir)
93 return
95 subdirs = []
96 onlinedir = None
97 have_index = False
98 for entry in sorted(os.listdir(scan_dir)):
99 full_entry = os.path.join(scan_dir, entry)
100 if os.path.isdir(full_entry):
101 subdirs.append(full_entry)
102 continue
104 if entry.endswith('.devhelp2'):
105 log(options, "Reading index from " + entry)
106 o = ReadDevhelp(scan_dir, entry)
107 # Prefer this location over possibly stale index.sgml
108 if o is not None:
109 onlinedir = o
110 have_index = True
112 if onlinedir and entry == "index.sgml":
113 log(options, "Reading index from index.sgml")
114 onlinedir = ReadIndex(dir, entry)
115 have_index = True
116 elif entry == "index.sgml.gz" and not os.path.exists(os.path.join(scan_dir, 'index.sgml')):
117 # debian/ubuntu started to compress this as index.sgml.gz :/
118 print(''' Please fix https://bugs.launchpad.net/ubuntu/+source/gtk-doc/+bug/77138 . For now run:
119 gunzip %s/%s
120 ''' % (scan_dir, entry))
121 elif entry.endswith('.devhelp2.gz') and not os.path.exists(full_entry[:-3]):
122 # debian/ubuntu started to compress this as *devhelp2.gz :/
123 print('''Please fix https://bugs.launchpad.net/ubuntu/+source/gtk-doc/+bug/1466210 . For now run:
124 gunzip %s/%s
125 ''' % (scan_dir, entry))
126 # we could consider supporting: gzip module
128 if have_index:
129 AddMap(scan_dir, onlinedir, options)
131 # Now recursively scan the subdirectories.
132 for subdir in subdirs:
133 ScanDirectory(subdir, options)
136 def ReadDevhelp(dir, file):
137 onlinedir = None
139 for line in common.open_text(os.path.join(dir, file)):
140 # online must come before chapter/functions
141 if '<chapters' in line or '<functions' in line:
142 break
143 match = re.search(r' online="([^"]*)"', line)
144 if match:
145 # Remove trailing non-directory component.
146 onlinedir = re.sub(r'(.*/).*', r'\1', match.group(1))
147 return onlinedir
150 def ReadIndex(dir, file):
151 onlinedir = None
153 for line in common.open_text(os.path.join(dir, file)):
154 # ONLINE must come before any ANCHORs
155 if '<ANCHOR' in line:
156 break
157 match = re.match(r'''^<ONLINE\s+href\s*=\s*"([^"]+)"\s*>''', line)
158 if match:
159 # Remove trailing non-directory component.
160 onlinedir = re.sub(r'''(.*/).*''', r'\1', match.groups(1))
161 return onlinedir
164 def AddMap(dir, onlinedir, options):
165 package = None
167 package = os.path.split(dir)[1]
168 if options.dest_dir != '' and dir.startswith(options.dest_dir):
169 dir = dir[len(options.dest_dir) - 1:]
171 if onlinedir:
172 log(options, "On-line location of %s." % onlinedir)
173 OnlineMap[package] = onlinedir
174 RevMap[onlinedir] = package
175 else:
176 log(options, "No On-line location for %s found" % package)
178 log(options, "Local location of $package: " + dir)
179 LocalMap[package] = dir
180 RevMap[dir] = package
183 def RelativizeLocalMap(dirname, options):
184 prefix = None
185 dir = None
187 dirname = os.path.realpath(dirname)
188 prefix = os.path.split(dirname)
189 for package, dir in LocalMap.items():
190 if dir.startswith(prefix):
191 dir = os.path.join("..", dir[len(prefix):])
192 LocalMap[package] = dir
193 log(options, "Relativizing local location of $package to " + dir)
196 def RebaseReferences(dirname, options):
197 for ifile in sorted(os.listdir(dirname)):
198 if ifile.endswith('.html'):
199 RebaseFile(os.path.join(dirname, ifile), options)
202 def RebaseFile(filename, options):
203 log(options, "Fixing file: " + filename)
204 regex = re.compile(r'''(<a(?:\s+\w+=(?:"[^"]*"|'[^']*'))*\s+href=")([^"]*)(")''',
205 flags=re.MULTILINE)
207 def repl_func(match):
208 return match.group(1) + RebaseLink(match.group(2), options) + match.group(3)
210 contents = common.open_text(filename).read()
211 processed = re.sub(regex, repl_func, contents)
212 newfilename = filename + '.new'
213 with common.open_text(newfilename, 'w') as h:
214 h.write(processed)
215 os.unlink(filename)
216 os.rename(newfilename, filename)
219 def RebaseLink(href, options):
220 match = re.match(r'^(.*/)([^/]*)$', href)
221 package = None
222 origdir = 'INVALID'
224 if match:
225 dir = origdir = match.group(1)
226 file = match.group(2)
227 if dir in RevMap:
228 package = RevMap[dir]
229 else:
230 match = re.match(r'\.\./([^/]+)', href)
231 if match is not None:
232 package = match.groups(1)
233 elif options.aggressive:
234 match = re.search(r'''([^/]+)/$''', href)
235 package = match.groups(1)
237 if package:
238 if options.online and package in OnlineMap:
239 dir = OnlineMap[package]
240 elif package in LocalMap:
241 dir = LocalMap[package]
242 href = os.path.join(dir, file)
243 else:
244 log(options, "Can't determine package for '%s'" % href)
246 if dir != origdir:
247 if origdir in Mapped:
248 Mapped[origdir][1] += 1
249 else:
250 Mapped[origdir] = [dir, 1]
251 return href
254 def PrintWhatWeHaveDone():
255 for origdir in sorted(iterkeys(Mapped)):
256 info = Mapped[origdir]
257 print(origdir, "->", info[0], "(%s)" % info[1])