70bd8d5d731266da430f749016df6c310a163d34
[lttng-docs.git] / tools / check.py
1 # The MIT License (MIT)
2 #
3 # Copyright (c) 2016 Philippe Proulx <pproulx@efficios.com>
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining a copy
6 # of this software and associated documentation files (the "Software"), to deal
7 # in the Software without restriction, including without limitation the rights
8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 # copies of the Software, and to permit persons to whom the Software is
10 # furnished to do so, subject to the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be included in
13 # all copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 # THE SOFTWARE.
22
23 from termcolor import colored
24 import lxml.etree as etree
25 import subprocess
26 import argparse
27 import os.path
28 import sys
29 import os
30
31
32 def _perror(msg, exit=True):
33 print('{} {}'.format(colored('Error:', 'red'), colored(msg, 'red', attrs=['bold'])),
34 file=sys.stderr)
35
36 if exit:
37 sys.exit(1)
38
39
40 def _pinfo(msg):
41 print('{} {}'.format(colored('::', 'blue'), colored(msg, 'blue', attrs=['bold'])))
42
43
44 def _get_script_dir():
45 return os.path.dirname(os.path.realpath(__file__))
46
47
48 class _Checker:
49 def __init__(self, infile, verbose):
50 self._infile = infile
51 self._verbose = verbose
52 self._has_error = False
53 self._set_paths()
54 self._pverbose('asciidoc -> DocBook')
55 self._build()
56 self._set_root()
57 self._check()
58
59 @property
60 def has_error(self):
61 return self._has_error
62
63 def _pverbose(self, msg):
64 if self._verbose:
65 _pinfo(msg)
66
67 def _perror(self, msg, fatal=False):
68 self._has_error = True
69 _perror(msg, fatal)
70
71 def _set_paths(self):
72 self._indir = os.path.dirname(self._infile)
73 self._imgexportdir = os.path.join(self._indir, 'images', 'export')
74 self._builddir = os.path.join(_get_script_dir(), 'check', os.path.basename(self._infile))
75 self._outfile = os.path.join(self._builddir, 'out.xml')
76
77 def _build(self):
78 conf = os.path.join(_get_script_dir(), 'asciidoc.check.conf')
79 os.makedirs(self._builddir, mode=0o755, exist_ok=True)
80 cmd = [
81 'asciidoc',
82 '-f', conf,
83 '-b', 'docbook',
84 '-o', self._outfile,
85 ]
86
87 if self._verbose:
88 cmd.append('-v')
89
90 cmd.append(self._infile)
91 res = subprocess.run(cmd)
92
93 if res.returncode != 0:
94 self._perror('asciidoc did not finish successfully', True)
95
96 def _set_root(self):
97 tree = etree.ElementTree(file=self._outfile)
98 self._root = tree.getroot()
99
100 def _check(self):
101 self._pverbose('Checking links')
102 self._check_links()
103 self._pverbose('Checking images')
104 self._check_images()
105
106 def _check_links(self):
107 sections_anchors = self._root.findall('.//section')
108 sections_anchors += self._root.findall('.//anchor')
109 sections_anchors += self._root.findall('.//glossary')
110 sections_anchors += self._root.findall('.//important')
111 sections_anchors += self._root.findall('.//tip')
112 sections_anchors += self._root.findall('.//caution')
113 sections_anchors += self._root.findall('.//warning')
114 sections_anchors += self._root.findall('.//note')
115 links = self._root.findall('.//link')
116 end_ids = set()
117
118 for sa in sections_anchors:
119 end_id = sa.get('id')
120
121 if sa.tag in ('section', 'anchor') and end_id is None:
122 self._perror('Found a section/anchor with no ID', True)
123
124 end_ids.add(end_id)
125
126 link_ends = set()
127
128 for link in links:
129 end = link.get('linkend')
130
131 if end is None:
132 self._perror('Found a link with no end', True)
133
134 link_ends.add(end)
135
136 has_error = False
137
138 for end in link_ends:
139 if end not in end_ids:
140 self._perror('Link end "{}" does not name a section/anchor ID'.format(end))
141
142 def _check_images(self):
143 image_datas = self._root.findall('.//imagedata')
144
145 for image_data in image_datas:
146 fileref = image_data.get('fileref')
147 path = os.path.join(self._imgexportdir, fileref)
148
149 if not os.path.isfile(path):
150 self._perror('Cannot find image "{}"'.format(fileref))
151
152
153 def _parse_args():
154 parser = argparse.ArgumentParser()
155 parser.add_argument('-v', '--verbose', action='store_true')
156 parser.add_argument('infile')
157 args = parser.parse_args()
158
159 if not os.path.isfile(args.infile):
160 _perror('"{}" is not an existing file'.format(args.infile))
161
162 return args
163
164
165 def _main():
166 args = _parse_args()
167 checker = _Checker(args.infile, args.verbose)
168
169 if checker.has_error:
170 return 1
171
172 print(colored('All good!', 'green', attrs=['bold']))
173
174 return 0
175
176
177 if __name__ == '__main__':
178 sys.exit(_main())
This page took 0.034816 seconds and 3 git commands to generate.