/[volute]/trunk/projects/ivoapub/ivoatex/update_generated.py
ViewVC logotype

Contents of /trunk/projects/ivoapub/ivoatex/update_generated.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3824 - (show annotations)
Mon Jan 23 14:30:21 2017 UTC (4 years, 6 months ago) by msdemlei
File MIME type: text/x-python
File size: 5795 byte(s)
ivoatex: changing ivoa.bst to use plain \url instead of \harvardurl

that's much easier to deal with in the presence of tth.


1 #!/usr/bin/env python
2 # Update all generated sections in a text file.
3 #
4 # This is part of ivoatex. See COPYING for the license.
5 #
6 # Generarated sections are between % GENERATED: <command>
7 # and % /GENERATED. They are supposed to contain the output of
8 # <command>. <command> get shell-expanded, but since it gets executed
9 # anyway, it's not even worth doing shell injection.
10 #
11 # When this script finishes, it either has updated all sections or
12 # stopped with an error message of a failed command, in which case the
13 # original file is unchanged.
14
15 from cStringIO import StringIO
16 import csv
17 import os
18 import re
19 import subprocess
20 import sys
21
22 try:
23 import requests
24 except ImportError:
25 # silently fail for now; !taptable will not work without requests, though
26 pass
27
28 class ExecError(Exception):
29 def __init__(self, command, stderr):
30 Exception.__init__(self, "Failed command %s"%repr(command))
31 self.command, self.stderr = command, stderr
32
33
34 def escape_for_TeX(tx):
35 """returns tx with TeX's standard active (and other magic) characters
36 escaped.
37 """
38 return tx.replace("\\", "$\backslash$"
39 ).replace("&", "\\&"
40 ).replace("#", "\\#"
41 ).replace("%", "\\%"
42 ).replace("_", "\\_"
43 ).replace('"', '{"}')
44
45
46 def cmd_taptable(table_name):
47 """returns an ivoatex-formatted table describing table_name in the
48 TAP sevice at $TAPURL.
49
50 This needs the requests module installed, and TAPURL must be defined
51 in the makefile.
52 """
53 tap_url = os.environ["TAPURL"]
54 reply = requests.get(tap_url+"/sync", params={
55 "LANG": "ADQL",
56 "REQUEST": "doQuery",
57 "QUERY": 'SELECT column_name, datatype, "size", description'
58 ' FROM TAP_SCHEMA.columns WHERE table_name=\'%s\''%table_name,
59 "FORMAT": "csv"})
60
61 res = ["\\begin{inlinetable}\n\\small"
62 r"\begin{tabular}{p{0.28\textwidth}p{0.2\textwidth}p{0.66\textwidth}}"
63 r"\sptablerule"
64 r"\multicolumn{3}{l}{\textit{Column names, ADQL types,",
65 r"and descriptions for the \texttt{%s} table}}\\"%table_name,
66 r"\sptablerule"]
67
68 for row in csv.DictReader(StringIO(reply.text)):
69 row = dict((key, escape_for_TeX(value))
70 for key, value in row.iteritems())
71 if row["size"]=="":
72 row["size"] = '(*)'
73 elif row["size"]=='1':
74 row["size"] = ''
75 else:
76 row["size"] = "(%s)"%row["size"]
77
78 res.append(r"""
79 \makebox[0pt][l]{\scriptsize\ttfamily %(column_name)s}&
80 \footnotesize %(datatype)s%(size)s&
81 %(description)s\\"""%row)
82
83 res.extend([
84 "\\sptablerule\n\\end{tabular}\n\\end{inlinetable}"])
85
86 return "\n".join(res)
87
88
89 def cmd_schemadoc(schema_name, dest_type):
90 """returns TeX source for the generated documentation of dest_type within
91 schema_name.
92
93 We cannot just use the output of the stylesheet, as TeX escapes in
94 XSLT1 are an inefficient nightmare.
95 """
96 output = subprocess.check_output(["xsltproc",
97 "--stringparam", "destType", dest_type,
98 "ivoatex/schemadoc.xslt", schema_name])
99 # for the TeX escaping, we simply assume there's no nesting
100 # of escaped sections, and no annotation uses our magic strings.
101 return "\\begin{generated}\n%s\n\\end{generated}\n"%(
102 re.sub("(?s)escape-for-TeX{{{(.*?)}}}",
103 lambda mat: escape_for_TeX(mat.group(1)), output))
104
105
106 def process_one_builtin(command):
107 """processes a GENERATED block containing a call to a builtin function.
108
109 In the GENERATED opening line, an internal call is signified with a
110 leading bang (which process_one already removes).
111
112 What's left is a command spec and blank-separated arguments. The command
113 spec is prepended with cmd_ and then used as a function name to call.
114 The remaining material is split and passed to the function as positional
115 arguments.
116
117 The function returns the return value of function, which must be a
118 string for this to work.
119 """
120 try:
121 parts = command.split()
122 print("Calling %s(%s)"%("cmd_"+parts[0], ", ".join(parts[1:])))
123 return globals()["cmd_"+parts[0]](*parts[1:])
124 except Exception, ex:
125 ex.command = command
126 raise
127
128
129 def process_one_exec(command):
130 """processes a GENERATED block containing a shell command.
131
132 command is the shell command as specified in the GENERATED's opening
133 line.
134
135 The output of the command is returned; in case of failures, an ExecError
136 is raised.
137 """
138 print("Executing %s"%command)
139 f = subprocess.Popen(command, shell=True,
140 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
141 close_fds=True, bufsize=-1)
142 stdout, stderr = f.communicate()
143
144 if f.returncode!=0:
145 raise ExecError(command, stderr)
146 return stdout
147
148
149 def process_one(match_obj):
150 """processes one GENERATED block, executing the specified command and
151 returning its output.
152
153 This is intended to be used as a callback within re.sub as executed
154 by process_all.
155 """
156 command = match_obj.group("command")
157 if command.startswith("!"):
158 result = process_one_builtin(command[1:])
159 else:
160 result = process_one_exec(command)
161
162 return ("%% GENERATED: %s\n"%(command.strip())
163 +result
164 +"\n% /GENERATED")
165
166
167 def process_all(content):
168 """replaces all GENERATED blocks within content.
169
170 Exceptions from within one of the recipes are propagated out.
171 """
172 return re.sub(r"(?sm)^%\s+GENERATED:\s+(?P<command>.*?)$"
173 ".*?"
174 r"%\s+/GENERATED",
175 process_one,
176 content)
177
178
179 def parse_command_line():
180 import argparse
181 parser = argparse.ArgumentParser(description="Update generated content"
182 " in a text file")
183 parser.add_argument("filename", action="store", type=str,
184 help="File to process (will be overwritten).")
185 return parser.parse_args()
186
187
188 def main():
189 args = parse_command_line()
190 with open(args.filename) as f:
191 content = f.read()
192
193 try:
194 content = process_all(content)
195 except ExecError, ex:
196 sys.stderr.write("Command %s failed. Message below. Aborting.\n"%
197 ex.command)
198 sys.stderr.write(ex.stderr+"\n")
199 sys.exit(1)
200
201 with open(args.filename, "w") as f:
202 f.write(content)
203
204
205 if __name__=="__main__":
206 main()

msdemlei@ari.uni-heidelberg.de
ViewVC Help
Powered by ViewVC 1.1.26