Commit | Line | Data |
---|---|---|
f0287ae1 PP |
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') | |
8d140568 PP |
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') | |
f0287ae1 PP |
115 | links = self._root.findall('.//link') |
116 | end_ids = set() | |
117 | ||
118 | for sa in sections_anchors: | |
119 | end_id = sa.get('id') | |
120 | ||
8d140568 | 121 | if sa.tag in ('section', 'anchor') and end_id is None: |
f0287ae1 PP |
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()) |