... | ... |
@@ -2,9 +2,154 @@ |
2 | 2 |
|
3 | 3 |
Cache and query the [OpenGL][] [registry][] locally. |
4 | 4 |
|
5 |
+`glregistry` is a Python program that glues together [HTTPS][] downloading, |
|
6 |
+[XPath][] querying, data accumulation/sorting, [`grep`][]ing, [color][] output, |
|
7 |
+and launching external [editor][]/[pager][] programs. |
|
8 |
+ |
|
9 |
+It tries to be a good Unix citizen by obeying relevant environment variables |
|
10 |
+and being [pipe][]-friendly, outputting newline-separated entries. Where |
|
11 |
+appropriate each line is in a widely supported format, usable with e.g. |
|
12 |
+[Vim][]'s [QuickFix][] ([`:cexpr`][]` system('glregistry audit')` or [`vim |
|
13 |
+-q`][]` <(glregistry audit)`) and [GNU Emacs][]' [Compilation Mode][] ([`M-x |
|
14 |
+compile`][]` glregistry audit`). |
|
15 |
+ |
|
16 |
+Note that only the OpenGL (not OpenGL ES) API, and only the core (not |
|
17 |
+compatibility) [profile][] is considered. |
|
18 |
+ |
|
5 | 19 |
[`glregistry`]: https://git.rcrnstn.net/rcrnstn/glregistry |
6 | 20 |
[OpenGL]: https://en.wikipedia.org/wiki/OpenGL |
7 | 21 |
[registry]: https://registry.khronos.org/OpenGL/ |
22 |
+[HTTPS]: https://en.wikipedia.org/wiki/HTTPS |
|
23 |
+[XPath]: https://en.wikipedia.org/wiki/XPath |
|
24 |
+[`grep`]: https://en.wikipedia.org/wiki/Grep |
|
25 |
+[color]: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors |
|
26 |
+[editor]: https://en.wikipedia.org/wiki/Text_editor |
|
27 |
+[pager]: https://en.wikipedia.org/wiki/Terminal_pager |
|
28 |
+[pipe]: https://en.wikipedia.org/wiki/Pipeline_(Unix) |
|
29 |
+[Vim]: https://en.wikipedia.org/wiki/Vim_(text_editor) |
|
30 |
+[QuickFix]: https://vimhelp.org/quickfix.txt.html |
|
31 |
+[`:cexpr`]: https://vimhelp.org/quickfix.txt.html#%3Acexpr |
|
32 |
+[`vim -q`]: https://vimhelp.org/starting.txt.html#-q |
|
33 |
+[GNU Emacs]: https://en.wikipedia.org/wiki/GNU_Emacs |
|
34 |
+[Compilation Mode]: https://www.gnu.org/software/emacs/manual/html_node/emacs/Compilation-Mode.html |
|
35 |
+[`M-x compile`]: https://www.gnu.org/software/emacs/manual/html_node/emacs/Compilation.html#index-compile-2624 |
|
36 |
+[profile]: https://www.khronos.org/opengl/wiki/OpenGL_Context#OpenGL_3.2_and_Profiles |
|
37 |
+ |
|
38 |
+## Usage |
|
39 |
+ |
|
40 |
+`glregistry --help`: |
|
41 |
+ |
|
42 |
+``` |
|
43 |
+glregistry 1.0 |
|
44 |
+ |
|
45 |
+Cache and query the OpenGL registry locally. |
|
46 |
+ |
|
47 |
+Usage: |
|
48 |
+ glregistry xml |
|
49 |
+ glregistry xml-path |
|
50 |
+ glregistry ext <extension> |
|
51 |
+ glregistry ext-path <extension> |
|
52 |
+ glregistry exts |
|
53 |
+ glregistry exts-download |
|
54 |
+ glregistry exts-all <name> |
|
55 |
+ glregistry type <type> |
|
56 |
+ glregistry value <enum> |
|
57 |
+ glregistry enum <value> |
|
58 |
+ glregistry supports <name> |
|
59 |
+ glregistry names [<support>] |
|
60 |
+ glregistry groups [<enum>] |
|
61 |
+ glregistry enums [<group>] |
|
62 |
+ glregistry enums-tree [<group>] |
|
63 |
+ glregistry params [<group>] |
|
64 |
+ glregistry params-tree [<group>] |
|
65 |
+ glregistry audit [<path>] |
|
66 |
+ glregistry audit-tree [<path>] |
|
67 |
+ glregistry -h|--help |
|
68 |
+ |
|
69 |
+Commands: |
|
70 |
+ xml |
|
71 |
+ Download the registry XML and open it with an editor. |
|
72 |
+ xml-path |
|
73 |
+ Download the registry XML and print its local path. |
|
74 |
+ ext <extension> |
|
75 |
+ Download the <extension> spec and open it with an editor. |
|
76 |
+ ext-path <extension> |
|
77 |
+ Download the <extension> spec and print its local path. |
|
78 |
+ exts |
|
79 |
+ Print the names of all extension specs. |
|
80 |
+ exts-download |
|
81 |
+ Download all extension specs. |
|
82 |
+ exts-all <name> |
|
83 |
+ Print all downloaded extensions that mention <name>. |
|
84 |
+ type <type> |
|
85 |
+ Print the definition of <type>. |
|
86 |
+ value <enum> |
|
87 |
+ Print the value of <enum>. |
|
88 |
+ enum <value> |
|
89 |
+ Print the enum(s) that has the given <value>, using exact string matching. |
|
90 |
+ supports <name> |
|
91 |
+ Print the OpenGL version or extension required to use <name>. |
|
92 |
+ names [<support>] |
|
93 |
+ Print the names introduced by the OpenGL version or extension <support> if |
|
94 |
+ given, or all names if omitted. The special values VERSION and EXTENSION |
|
95 |
+ print the names introduced by all versions or all extensions respectively. |
|
96 |
+ groups [<enum>] |
|
97 |
+ Print the groups of <enum> if given, or all groups if omitted. |
|
98 |
+ enums [<group>] |
|
99 |
+ Print the enums in <group> if given, or all enums if omitted. |
|
100 |
+ enums-tree [<group>] |
|
101 |
+ Print the enums in <group> if given, or all enums if omitted, sorted on |
|
102 |
+ support, in a tree. |
|
103 |
+ params [<group>] |
|
104 |
+ Print the parameter names of <group> if given, or all parameter names if |
|
105 |
+ omitted. |
|
106 |
+ params-tree [<group>] |
|
107 |
+ Print the parameter names of <group> if given, or all parameter names if |
|
108 |
+ omitted, sorted on count, together with the commands sorted on support, in |
|
109 |
+ a tree. |
|
110 |
+ audit [<path>] |
|
111 |
+ Search files in <path> if given, or the current directory if omitted, |
|
112 |
+ recursively for OpenGL API names and print them sorted on location, |
|
113 |
+ support, and name, in a list. |
|
114 |
+ audit-tree [<path>] |
|
115 |
+ Search files in <path> if given, or the current directory if omitted, |
|
116 |
+ recursively for OpenGL API names and print them sorted on support, name, |
|
117 |
+ and location, in a tree. |
|
118 |
+ |
|
119 |
+Environment variables: |
|
120 |
+ GLREGISTRY_CACHE |
|
121 |
+ The directory to cache files in. Defaults to `$XDG_CACHE_HOME/glregistry` |
|
122 |
+ or, if `$XDG_CACHE_HOME` is not defined, `$HOME/.cache/glregistry`. |
|
123 |
+ GLREGISTRY_EDITOR |
|
124 |
+ The editor to use when opening files. Defaults to `$EDITOR` or, if |
|
125 |
+ `$EDITOR` is not defined, `editor` if it exists in `$PATH`, else `vi`. The |
|
126 |
+ value is interpreted by the shell. |
|
127 |
+ GLREGISTRY_PAGER |
|
128 |
+ The pager to use when viewing output. Defaults to `$PAGER` or, if `$PAGER` |
|
129 |
+ is not defined, `pager` if it exists in `$PATH`, else `less` . The value is |
|
130 |
+ interpreted by the shell. If the `$LESS` environment variable is unset, it |
|
131 |
+ is set to `FR`. |
|
132 |
+``` |
|
133 |
+ |
|
134 |
+## References |
|
135 |
+ |
|
136 |
+Note that some of these might be out of date. |
|
137 |
+ |
|
138 |
+- <https://github.com/KhronosGroup/OpenGL-Registry/blob/main/xml/readme.pdf> |
|
139 |
+- <https://github.com/KhronosGroup/OpenGL-Registry/blob/main/xml/registry.rnc> |
|
140 |
+- <https://github.com/KhronosGroup/OpenGL-Registry/blob/main/xml/reg.py> |
|
141 |
+- <https://github.com/KhronosGroup/OpenGL-Registry/blob/main/xml/genheaders.py> |
|
142 |
+- <https://github.com/KhronosGroup/OpenGL-Registry/blob/main/extensions/registry.py> |
|
143 |
+- <https://github.com/KhronosGroup/OpenGL-Registry/blob/main/docs/syntaxrules.txt> |
|
144 |
+- <https://github.com/KhronosGroup/OpenGL-Registry/blob/main/docs/promoting.html> |
|
145 |
+- <https://github.com/KhronosGroup/OpenGL-Registry/blob/main/docs/enums.html> |
|
146 |
+- <https://github.com/KhronosGroup/OpenGL-Registry/issues/481> |
|
147 |
+ |
|
148 |
+See also: |
|
149 |
+ |
|
150 |
+- The community-driven [OpenGL Hardware Database][]. |
|
151 |
+ |
|
152 |
+[OpenGL Hardware Database]: https://opengl.gpuinfo.org |
|
8 | 153 |
|
9 | 154 |
## Install |
10 | 155 |
|
11 | 156 |
new file mode 100755 |
... | ... |
@@ -0,0 +1,497 @@ |
1 |
+#!/usr/bin/env python3 |
|
2 |
+ |
|
3 |
+ |
|
4 |
+## Help |
|
5 |
+""" |
|
6 |
+glregistry 1.0 |
|
7 |
+ |
|
8 |
+Cache and query the OpenGL registry locally. |
|
9 |
+ |
|
10 |
+Usage: |
|
11 |
+ glregistry xml |
|
12 |
+ glregistry xml-path |
|
13 |
+ glregistry ext <extension> |
|
14 |
+ glregistry ext-path <extension> |
|
15 |
+ glregistry exts |
|
16 |
+ glregistry exts-download |
|
17 |
+ glregistry exts-all <name> |
|
18 |
+ glregistry type <type> |
|
19 |
+ glregistry value <enum> |
|
20 |
+ glregistry enum <value> |
|
21 |
+ glregistry supports <name> |
|
22 |
+ glregistry names [<support>] |
|
23 |
+ glregistry groups [<enum>] |
|
24 |
+ glregistry enums [<group>] |
|
25 |
+ glregistry enums-tree [<group>] |
|
26 |
+ glregistry params [<group>] |
|
27 |
+ glregistry params-tree [<group>] |
|
28 |
+ glregistry audit [<path>] |
|
29 |
+ glregistry audit-tree [<path>] |
|
30 |
+ glregistry -h|--help |
|
31 |
+ |
|
32 |
+Commands: |
|
33 |
+ xml |
|
34 |
+ Download the registry XML and open it with an editor. |
|
35 |
+ xml-path |
|
36 |
+ Download the registry XML and print its local path. |
|
37 |
+ ext <extension> |
|
38 |
+ Download the <extension> spec and open it with an editor. |
|
39 |
+ ext-path <extension> |
|
40 |
+ Download the <extension> spec and print its local path. |
|
41 |
+ exts |
|
42 |
+ Print the names of all extension specs. |
|
43 |
+ exts-download |
|
44 |
+ Download all extension specs. |
|
45 |
+ exts-all <name> |
|
46 |
+ Print all downloaded extensions that mention <name>. |
|
47 |
+ type <type> |
|
48 |
+ Print the definition of <type>. |
|
49 |
+ value <enum> |
|
50 |
+ Print the value of <enum>. |
|
51 |
+ enum <value> |
|
52 |
+ Print the enum(s) that has the given <value>, using exact string matching. |
|
53 |
+ supports <name> |
|
54 |
+ Print the OpenGL version or extension required to use <name>. |
|
55 |
+ names [<support>] |
|
56 |
+ Print the names introduced by the OpenGL version or extension <support> if |
|
57 |
+ given, or all names if omitted. The special values VERSION and EXTENSION |
|
58 |
+ print the names introduced by all versions or all extensions respectively. |
|
59 |
+ groups [<enum>] |
|
60 |
+ Print the groups of <enum> if given, or all groups if omitted. |
|
61 |
+ enums [<group>] |
|
62 |
+ Print the enums in <group> if given, or all enums if omitted. |
|
63 |
+ enums-tree [<group>] |
|
64 |
+ Print the enums in <group> if given, or all enums if omitted, sorted on |
|
65 |
+ support, in a tree. |
|
66 |
+ params [<group>] |
|
67 |
+ Print the parameter names of <group> if given, or all parameter names if |
|
68 |
+ omitted. |
|
69 |
+ params-tree [<group>] |
|
70 |
+ Print the parameter names of <group> if given, or all parameter names if |
|
71 |
+ omitted, sorted on count, together with the commands sorted on support, in |
|
72 |
+ a tree. |
|
73 |
+ audit [<path>] |
|
74 |
+ Search files in <path> if given, or the current directory if omitted, |
|
75 |
+ recursively for OpenGL API names and print them sorted on location, |
|
76 |
+ support, and name, in a list. |
|
77 |
+ audit-tree [<path>] |
|
78 |
+ Search files in <path> if given, or the current directory if omitted, |
|
79 |
+ recursively for OpenGL API names and print them sorted on support, name, |
|
80 |
+ and location, in a tree. |
|
81 |
+ |
|
82 |
+Environment variables: |
|
83 |
+ GLREGISTRY_CACHE |
|
84 |
+ The directory to cache files in. Defaults to `$XDG_CACHE_HOME/glregistry` |
|
85 |
+ or, if `$XDG_CACHE_HOME` is not defined, `$HOME/.cache/glregistry`. |
|
86 |
+ GLREGISTRY_EDITOR |
|
87 |
+ The editor to use when opening files. Defaults to `$EDITOR` or, if |
|
88 |
+ `$EDITOR` is not defined, `editor` if it exists in `$PATH`, else `vi`. The |
|
89 |
+ value is interpreted by the shell. |
|
90 |
+ GLREGISTRY_PAGER |
|
91 |
+ The pager to use when viewing output. Defaults to `$PAGER` or, if `$PAGER` |
|
92 |
+ is not defined, `pager` if it exists in `$PATH`, else `less` . The value is |
|
93 |
+ interpreted by the shell. If the `$LESS` environment variable is unset, it |
|
94 |
+ is set to `FR`. |
|
95 |
+""" |
|
96 |
+ |
|
97 |
+ |
|
98 |
+## Imports |
|
99 |
+import os |
|
100 |
+import sys |
|
101 |
+import collections |
|
102 |
+import re |
|
103 |
+import subprocess |
|
104 |
+import shutil |
|
105 |
+import shlex |
|
106 |
+import urllib.request |
|
107 |
+import docopt |
|
108 |
+from lxml import etree |
|
109 |
+ |
|
110 |
+ |
|
111 |
+## Constants |
|
112 |
+REGISTRY_URL = 'https://registry.khronos.org/OpenGL/' |
|
113 |
+XML_PATH = 'xml/gl.xml' |
|
114 |
+REGEX = r'\b(gl|GL_)[0-9A-Z][0-9A-Za-z_]+\b' |
|
115 |
+EXCLUDE_DIRS = ['.?*', '_*'] |
|
116 |
+EXCLUDE_FILES = ['README*', 'TODO*'] |
|
117 |
+INDENT = 2 |
|
118 |
+ENV_XDG = lambda var, default: ( |
|
119 |
+ os.environ.get(f'GLREGISTRY_{var}') or |
|
120 |
+ os.path.join( |
|
121 |
+ ( |
|
122 |
+ os.environ.get(f'XDG_{var}_HOME') or |
|
123 |
+ os.path.expanduser(default) |
|
124 |
+ ), |
|
125 |
+ 'glregistry', |
|
126 |
+ ) |
|
127 |
+) |
|
128 |
+ENV_PRG = lambda var, default: ( |
|
129 |
+ os.environ.get(f'GLREGISTRY_{var}') or |
|
130 |
+ os.environ.get(var) or |
|
131 |
+ shutil.which(var.lower()) or |
|
132 |
+ default |
|
133 |
+) |
|
134 |
+CACHE = ENV_XDG('CACHE', os.path.join('~', '.cache')) |
|
135 |
+EDITOR = ENV_PRG('EDITOR', 'vi') |
|
136 |
+PAGER = ENV_PRG('PAGER', 'less') |
|
137 |
+LESS = os.environ.get('LESS') or 'FR' |
|
138 |
+IN = lambda a, v, s: f"contains(concat('{s}',@{a},'{s}'),'{s}{v}{s}')" |
|
139 |
+MAYBE = lambda a, v: f"(@{a}='{v}' or not(@{a}))" |
|
140 |
+TYPES = "/registry/types" |
|
141 |
+ENUMS = "/registry/enums" |
|
142 |
+COMMANDS = "/registry/commands" |
|
143 |
+CATEGORY_ATTRIBS = { |
|
144 |
+ 'VERSION': [ |
|
145 |
+ "/registry/feature[@api='gl']", |
|
146 |
+ 'number', |
|
147 |
+ ], |
|
148 |
+ 'EXTENSION': [ |
|
149 |
+ f"/registry/extensions/extension[{IN('supported','gl','|')}]", |
|
150 |
+ 'name', |
|
151 |
+ ], |
|
152 |
+} |
|
153 |
+REQUIRE = f"require[{MAYBE('api','gl')} and {MAYBE('profile','core')}]" |
|
154 |
+ |
|
155 |
+ |
|
156 |
+## Helpers |
|
157 |
+ |
|
158 |
+ |
|
159 |
+### `log` |
|
160 |
+def log(*args, **kwargs): |
|
161 |
+ print(*args, file=sys.stderr, flush=True, **kwargs) |
|
162 |
+ |
|
163 |
+ |
|
164 |
+### `edit` |
|
165 |
+def edit(paths): |
|
166 |
+ if EDITOR and sys.stdout.isatty(): |
|
167 |
+ args = ' '.join([EDITOR, *map(shlex.quote, paths)]) |
|
168 |
+ subprocess.run(args, shell=True) |
|
169 |
+ else: |
|
170 |
+ for path in paths: |
|
171 |
+ with open(path) as f: |
|
172 |
+ shutil.copyfileobj(f, sys.stdout) |
|
173 |
+ |
|
174 |
+ |
|
175 |
+### `page` |
|
176 |
+def page(lines): |
|
177 |
+ lines = ''.join(f'{line}\n' for line in lines) |
|
178 |
+ if lines and PAGER and sys.stdout.isatty(): |
|
179 |
+ args = f'LESS={shlex.quote(LESS)} {PAGER}' |
|
180 |
+ subprocess.run(args, shell=True, text=True, input=lines) |
|
181 |
+ else: |
|
182 |
+ sys.stdout.write(lines) |
|
183 |
+ |
|
184 |
+ |
|
185 |
+### `indentjoin` |
|
186 |
+def indentjoin(indent, sep, parts): |
|
187 |
+ return ' ' * INDENT * indent + sep.join(map(str, parts)) |
|
188 |
+ |
|
189 |
+ |
|
190 |
+### `removeprefix` |
|
191 |
+def removeprefix(prefix, string): |
|
192 |
+ if string.startswith(prefix): |
|
193 |
+ return string[len(prefix):] |
|
194 |
+ return string |
|
195 |
+ |
|
196 |
+ |
|
197 |
+### `download` |
|
198 |
+def download(path, exit_on_failure=True): |
|
199 |
+ remote = urllib.parse.urljoin(REGISTRY_URL, path) |
|
200 |
+ local = os.path.join (CACHE, path) |
|
201 |
+ if not os.path.exists(local): |
|
202 |
+ try: |
|
203 |
+ log(f"Downloading '{path}' ... ", end='') |
|
204 |
+ with urllib.request.urlopen(remote) as response: |
|
205 |
+ os.makedirs(os.path.dirname(local), exist_ok=True) |
|
206 |
+ with open(local, 'wb') as f: |
|
207 |
+ shutil.copyfileobj(response, f) |
|
208 |
+ except urllib.error.URLError as error: |
|
209 |
+ log(error.reason) |
|
210 |
+ if exit_on_failure: |
|
211 |
+ exit(1) |
|
212 |
+ else: |
|
213 |
+ log(response.reason) |
|
214 |
+ return local |
|
215 |
+ |
|
216 |
+ |
|
217 |
+### `grep` |
|
218 |
+def grep( |
|
219 |
+ path=None, |
|
220 |
+ regex=REGEX, |
|
221 |
+ exclude_dirs=EXCLUDE_DIRS, |
|
222 |
+ exclude_files=EXCLUDE_FILES, |
|
223 |
+ silent=False, |
|
224 |
+): |
|
225 |
+ path = path if path else '.' |
|
226 |
+ cmd = ['grep', '-EIrno'] |
|
227 |
+ exclude_dirs = [f'--exclude-dir={exclude}' for exclude in exclude_dirs] |
|
228 |
+ exclude_files = [f'--exclude={exclude}' for exclude in exclude_files] |
|
229 |
+ process = subprocess.run( |
|
230 |
+ [*cmd, *exclude_dirs, *exclude_files, regex, path], |
|
231 |
+ stdout=subprocess.PIPE, |
|
232 |
+ stderr=subprocess.DEVNULL if silent else None, |
|
233 |
+ text=True, |
|
234 |
+ ) |
|
235 |
+ for string in process.stdout.splitlines(): |
|
236 |
+ string = removeprefix(f'.{os.path.sep}', string) |
|
237 |
+ file, line, name = string.split(':', 2) |
|
238 |
+ line = int(line) |
|
239 |
+ yield file, line, name |
|
240 |
+ |
|
241 |
+ |
|
242 |
+## Commands |
|
243 |
+ |
|
244 |
+ |
|
245 |
+### `xml_` |
|
246 |
+def xml_(): |
|
247 |
+ return etree.parse(download(XML_PATH)) |
|
248 |
+ |
|
249 |
+ |
|
250 |
+### `xml` |
|
251 |
+def xml(): |
|
252 |
+ return [download(XML_PATH)] |
|
253 |
+ |
|
254 |
+ |
|
255 |
+### `ext_` |
|
256 |
+def ext_(extension): |
|
257 |
+ prefix, vendor, name = extension.split('_', 2) |
|
258 |
+ if prefix != 'GL': |
|
259 |
+ log("Extension names must start with 'GL_'.") |
|
260 |
+ exit(1) |
|
261 |
+ return f'extensions/{vendor}/{vendor}_{name}.txt' |
|
262 |
+ |
|
263 |
+ |
|
264 |
+### `ext` |
|
265 |
+def ext(extension): |
|
266 |
+ return [download(ext_(extension))] |
|
267 |
+ |
|
268 |
+ |
|
269 |
+### `exts` |
|
270 |
+def exts(xml): |
|
271 |
+ category, attrib = CATEGORY_ATTRIBS['EXTENSION'] |
|
272 |
+ exts = xml.xpath(f"{category}/@{attrib}") |
|
273 |
+ return sorted(exts) |
|
274 |
+ |
|
275 |
+ |
|
276 |
+### `exts_download` |
|
277 |
+def exts_download(xml): |
|
278 |
+ for ext in exts(xml): |
|
279 |
+ download(ext_(ext), exit_on_failure=False) |
|
280 |
+ return [] |
|
281 |
+ |
|
282 |
+ |
|
283 |
+### `exts_all` |
|
284 |
+def exts_all(xml, name): |
|
285 |
+ # exts_download(xml) |
|
286 |
+ exts_all = set( |
|
287 |
+ 'GL_' + os.path.splitext(os.path.basename(file))[0] |
|
288 |
+ for name in set([ |
|
289 |
+ name, |
|
290 |
+ removeprefix('gl', name), |
|
291 |
+ removeprefix('GL_', name), |
|
292 |
+ ]) |
|
293 |
+ for file, *_ in |
|
294 |
+ grep(os.path.join(CACHE, 'extensions'), rf'\b{name}\b', [], []) |
|
295 |
+ ) |
|
296 |
+ return sorted(exts_all) |
|
297 |
+ |
|
298 |
+ |
|
299 |
+### `type` |
|
300 |
+def type(xml, type): |
|
301 |
+ return [xml.xpath(f"string({TYPES}/type/name[text()='{type}']/..)")] |
|
302 |
+ |
|
303 |
+ |
|
304 |
+### `value` |
|
305 |
+def value(xml, enum): |
|
306 |
+ return xml.xpath(f"{ENUMS}/enum[@name='{enum}']/@value") |
|
307 |
+ |
|
308 |
+ |
|
309 |
+### `enum` |
|
310 |
+def enum(xml, value): |
|
311 |
+ enum = xml.xpath(f"{ENUMS}/enum[@value='{value}']/@name") |
|
312 |
+ return sorted(enum) |
|
313 |
+ |
|
314 |
+ |
|
315 |
+### `supports` |
|
316 |
+def supports(xml, name): |
|
317 |
+ category, attrib = CATEGORY_ATTRIBS['EXTENSION'] |
|
318 |
+ if xml.xpath(f"{category}[@{attrib}='{name}']"): |
|
319 |
+ return ['EXTENSION'] |
|
320 |
+ supports_ = [ |
|
321 |
+ support |
|
322 |
+ for category, attrib in CATEGORY_ATTRIBS.values() |
|
323 |
+ for support in |
|
324 |
+ xml.xpath(f"{category}/{REQUIRE}/*[@name='{name}']/../../@{attrib}") |
|
325 |
+ ] |
|
326 |
+ return sorted(supports_) |
|
327 |
+ |
|
328 |
+ |
|
329 |
+### `names` |
|
330 |
+def names(xml, support=None): |
|
331 |
+ if support in CATEGORY_ATTRIBS.keys(): |
|
332 |
+ category_attribs = [ |
|
333 |
+ [category, ""] |
|
334 |
+ for category, _ in [CATEGORY_ATTRIBS[support]] |
|
335 |
+ ] |
|
336 |
+ elif support: |
|
337 |
+ category_attribs = [ |
|
338 |
+ [category, f"[@{attrib}='{support}']"] |
|
339 |
+ for category, attrib in CATEGORY_ATTRIBS.values() |
|
340 |
+ ] |
|
341 |
+ else: |
|
342 |
+ category_attribs = [ |
|
343 |
+ [category, ""] |
|
344 |
+ for category, _ in CATEGORY_ATTRIBS.values() |
|
345 |
+ ] |
|
346 |
+ names = set( |
|
347 |
+ name |
|
348 |
+ for category, attrib in category_attribs |
|
349 |
+ for name in |
|
350 |
+ xml.xpath(f"{category}{attrib}/{REQUIRE}/*/@name") |
|
351 |
+ ) |
|
352 |
+ return sorted(names) |
|
353 |
+ |
|
354 |
+ |
|
355 |
+### `groups` |
|
356 |
+def groups(xml, enum=None): |
|
357 |
+ name = f"[@name='{enum}']" if enum else "" |
|
358 |
+ return sorted(set( |
|
359 |
+ group |
|
360 |
+ for groups in xml.xpath(f"{ENUMS}/enum{name}/@group") |
|
361 |
+ for group in groups.split(',') |
|
362 |
+ )) |
|
363 |
+ |
|
364 |
+ |
|
365 |
+### `enums_` |
|
366 |
+def enums_(xml, group=None): |
|
367 |
+ group = f"[{IN('group',group,',')}]" if group else "" |
|
368 |
+ enums_ = collections.defaultdict(list) |
|
369 |
+ for enum in xml.xpath(f"{ENUMS}/enum{group}/@name"): |
|
370 |
+ supports_ = supports(xml, enum) |
|
371 |
+ if supports_: |
|
372 |
+ enums_[tuple(supports_)].append(enum) |
|
373 |
+ return enums_ |
|
374 |
+ |
|
375 |
+ |
|
376 |
+### `enums` |
|
377 |
+def enums(xml, group=None): |
|
378 |
+ enums = [ |
|
379 |
+ enum |
|
380 |
+ for _, enums in enums_(xml, group).items() |
|
381 |
+ for enum in enums |
|
382 |
+ ] |
|
383 |
+ return sorted(enums) |
|
384 |
+ |
|
385 |
+ |
|
386 |
+### `enums_tree` |
|
387 |
+def enums_tree(xml, group=None): |
|
388 |
+ for supports, enums in sorted(enums_(xml, group).items()): |
|
389 |
+ yield indentjoin(0, ',', supports) |
|
390 |
+ for enum in sorted(enums): |
|
391 |
+ yield indentjoin(1, '', [enum]) |
|
392 |
+ |
|
393 |
+ |
|
394 |
+### `params_` |
|
395 |
+def params_(xml, group=None): |
|
396 |
+ group = f"[@group='{group}']" if group else "" |
|
397 |
+ counts = collections.defaultdict(int) |
|
398 |
+ params_ = collections.defaultdict(lambda: collections.defaultdict(list)) |
|
399 |
+ for xmlcommand in xml.xpath(f"{COMMANDS}/command/param{group}/.."): |
|
400 |
+ command = xmlcommand.xpath(f"string(proto/name)") |
|
401 |
+ supports_ = supports(xml, command) |
|
402 |
+ if supports_: |
|
403 |
+ for xmlparam in xmlcommand.xpath(f"param{group}"): |
|
404 |
+ param = xmlparam.xpath(f"string(name)") |
|
405 |
+ params_[param][tuple(supports_)].append(command) |
|
406 |
+ counts[param] -= 1 |
|
407 |
+ return { |
|
408 |
+ (count, param): params_[param] |
|
409 |
+ for param, count in counts.items() |
|
410 |
+ } |
|
411 |
+ |
|
412 |
+ |
|
413 |
+### `params` |
|
414 |
+def params(xml, group=None): |
|
415 |
+ params = [param for (_, param), _ in params_(xml, group).items()] |
|
416 |
+ return sorted(params) |
|
417 |
+ |
|
418 |
+ |
|
419 |
+### `params_tree` |
|
420 |
+def params_tree(xml, group=None): |
|
421 |
+ for (count, param), occurences in sorted(params_(xml, group).items()): |
|
422 |
+ yield indentjoin(0, ':', [ |
|
423 |
+ param, |
|
424 |
+ -count, |
|
425 |
+ ]) |
|
426 |
+ for supports_, commands in sorted(occurences.items()): |
|
427 |
+ yield indentjoin(1, ',', supports_) |
|
428 |
+ for command in sorted(commands): |
|
429 |
+ yield indentjoin(2, '', [command]) |
|
430 |
+ |
|
431 |
+ |
|
432 |
+### `audit_` |
|
433 |
+def audit_(xml, path=None): |
|
434 |
+ audit_ = collections.defaultdict(lambda: collections.defaultdict(list)) |
|
435 |
+ for file, line, name in grep(path): |
|
436 |
+ supports_ = supports(xml, name) |
|
437 |
+ if not supports_: |
|
438 |
+ supports_ = ['UNSUPPORTED'] |
|
439 |
+ audit_[tuple(supports_)][name].append([file, line]) |
|
440 |
+ return audit_ |
|
441 |
+ |
|
442 |
+ |
|
443 |
+### `audit` |
|
444 |
+def audit(xml, path=None): |
|
445 |
+ for file, line, supports, name in sorted( |
|
446 |
+ [file, line, supports, name] |
|
447 |
+ for supports, names in audit_(xml, path).items() |
|
448 |
+ for name, locations in names.items() |
|
449 |
+ for file, line in locations |
|
450 |
+ ): |
|
451 |
+ yield indentjoin(0, ':', [ |
|
452 |
+ file, |
|
453 |
+ line, |
|
454 |
+ indentjoin(0, ',', supports), |
|
455 |
+ name |
|
456 |
+ ]) |
|
457 |
+ |
|
458 |
+ |
|
459 |
+### `audit_tree` |
|
460 |
+def audit_tree(xml, path=None): |
|
461 |
+ for supports, names in sorted(audit_(xml, path).items()): |
|
462 |
+ yield indentjoin(0, ',', supports) |
|
463 |
+ for name, locations in sorted(names.items()): |
|
464 |
+ yield indentjoin(1, '', [name]) |
|
465 |
+ for file, line in sorted(locations): |
|
466 |
+ yield indentjoin(2, ':', [ |
|
467 |
+ file, |
|
468 |
+ line, |
|
469 |
+ ]) |
|
470 |
+ |
|
471 |
+ |
|
472 |
+## Main |
|
473 |
+def main(): |
|
474 |
+ args = docopt.docopt(__doc__) |
|
475 |
+ if args['xml']: edit(xml ()) |
|
476 |
+ if args['xml-path']: page(xml ()) |
|
477 |
+ if args['ext']: edit(ext (args['<extension>'])) |
|
478 |
+ if args['ext-path']: page(ext (args['<extension>'])) |
|
479 |
+ if args['exts']: page(exts (xml_())) |
|
480 |
+ if args['exts-download']: page(exts_download(xml_())) |
|
481 |
+ if args['exts-all']: page(exts_all (xml_(), args['<name>'])) |
|
482 |
+ if args['type']: page(type (xml_(), args['<type>'])) |
|
483 |
+ if args['value']: page(value (xml_(), args['<enum>'])) |
|
484 |
+ if args['enum']: page(enum (xml_(), args['<value>'])) |
|
485 |
+ if args['supports']: page(supports (xml_(), args['<name>'])) |
|
486 |
+ if args['names']: page(names (xml_(), args['<support>'])) |
|
487 |
+ if args['groups']: page(groups (xml_(), args['<enum>'])) |
|
488 |
+ if args['enums']: page(enums (xml_(), args['<group>'])) |
|
489 |
+ if args['enums-tree']: page(enums_tree (xml_(), args['<group>'])) |
|
490 |
+ if args['params']: page(params (xml_(), args['<group>'])) |
|
491 |
+ if args['params-tree']: page(params_tree (xml_(), args['<group>'])) |
|
492 |
+ if args['audit']: page(audit (xml_(), args['<path>'])) |
|
493 |
+ if args['audit-tree']: page(audit_tree (xml_(), args['<path>'])) |
|
494 |
+ |
|
495 |
+ |
|
496 |
+if __name__ == '__main__': |
|
497 |
+ main() |