1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 """Utilities for HTML generation on the LIGO Data Grid.
20
21 This module provides some useful functions for buildling HTML content
22 on the LIGO Data Grid, and a few extensions to glue.markup to streamline
23 GEO/LIGO tools that use very similar web interfaces.
24 """
25
26
27
28
29
30 from __future__ import division
31
32 import re
33 import os
34 import sys
35 import socket
36
37 from glue import markup
38
39 from pylal import git_version
40
41 __author__ = "Duncan M. Macleod <duncan.macleod@ligo.org>"
42 __version__ = "git id %s" % git_version.id
43 __date__ = git_version.date
44
45 __all__ = ["about_page", "build_page", "get_web_server", "simple_page",\
46 "summary_page", "write_glossary", "write_menu", "write_table"]
47
48
49
50
51
53
54 """
55 Write table into glue.markup.page object. headers are written with <th>,
56 multiple columns of data are written with <td>.
57
58 Arguments:
59
60 page : glue.markup.page
61 page object into which to write table
62 headers : list
63 list of table header elements
64 data : list
65 list (or nested list) of table data elements, list of lists used for
66 multiple rows
67
68 Keyword arguments:
69
70 classdict : dict
71 dict containing tag:class pairs for table, td, th, and td HTML tags
72 "table":"list" prints table with headers and data side-by-side,
73 all others print all headers in one row, then all data in one row
74 (use list of lists for multiple rows).
75 """
76
77
78 tclass = classdict.get("table","")
79 rclass = classdict.get("tr","")
80 hclass = classdict.get("th","")
81 dclass = classdict.get("td","")
82
83 page = markup.page()
84
85
86 page.table(class_=tclass)
87
88
89 if tclass == "list":
90 for i in range(len(headers)):
91 page.tr(class_=rclass)
92 page.th(str(headers[i]), class_=hclass)
93 page.td(str(data[i]), class_=classdict.get(str(data[i]),dclass))
94 page.tr.close()
95
96
97
98 else:
99 page.tr(class_=rclass)
100 if len(headers)==1:
101 page.th(str(headers[0]), colspan="100%", class_=hclass)
102 else:
103 for n in headers:
104 page.th(str(n), class_=hclass)
105 page.tr.close()
106
107 if data and not re.search("list",str(type(data[0]))):
108 data = [data]
109
110 for row in data:
111 page.tr(class_=rclass)
112 for item in map(str, row):
113 page.td(item, class_=classdict.get(item,dclass))
114
115 page.table.close()
116
117 return page
118
119
120
121
122
124
125 """
126 Returns glue.markup.page object for <div id_="menubar">, constructing menu
127 in HTML.
128
129 Arguments:
130
131 sections : list
132 ordered list of menu entry names
133 pages : dict
134 dict of section:href pairs holding link paths for each element of
135 sections list
136
137 Keyword arguments:
138
139 current : str
140 element of sections list to identify with class="open"
141 classdict : dict
142 dict of tag:class pairs for setting HTML tags
143
144 """
145
146 page = markup.page()
147 page.div(id_="menubar", class_=classdict.get("div", ""))
148
149 for i,sec in enumerate(sections):
150
151 if sec == current and not re.search("open", classdict.get("a", "")):
152 cl = classdict["a"] + " open"
153 else:
154 cl = classdict["a"]
155
156
157 if pages[sec].endswith("/index.html"):
158 href = pages[sec][:-11]
159 else:
160 href = pages[sec]
161
162
163 page.a(sec, id_="a_%d" % i, class_=cl, href=href)
164
165 page.div.close()
166
167 return page
168
169
170
171
172
173 -def write_glossary(entries, htag="h1",\
174 classdict={"p":"line", "h4":"glossary closed"}):
175 """
176 Write a glossary of DQ terms into the glue.markup.page object page using the
177 list of (term,definition) tuples terms.
178
179 Arguments:
180
181 entries : dict
182 dict of term:definition pairs for inclusion in glossary
183
184 Keyword arguments:
185
186 htag : str
187 HTML tag to use for header, default <h1>
188 classdict : dict
189 dict of tag:class pairs for HTML class assignment
190 """
191
192 page = markup.page()
193
194
195 getattr(page, htag)("Glossary", id_="%s_glossary" % htag)
196 page.div(id_="div_glossary", style="display: block;",\
197 class_=classdict.get("div", ""), onclick="toggleVisible();")
198 page.p("This section gives a glossary of terms relevant to this page.",\
199 class_=classdict.get("p", ""))
200 lvwiki = "https://www.lsc-group.phys.uwm.edu/ligovirgo/cbcnote/Acronyms"
201 page.p("The LIGO-Virgo acronym wiki can be found on %s."\
202 % markup.oneliner.a("this page", href=lvwiki),\
203 class_=classdict.get("p", ""))
204
205
206 terms = sorted(entries.keys())
207 for i,term in enumerate(terms):
208 page.h4(term, id_="glossaryh4_%d" % i, class_=classdict.get("h4", ""))
209 page.div(entries[term], id_="div_%d" % i, style="display: none;",\
210 class_="glossary")
211
212 page.div.close()
213
214 return page
215
216
217
218
219
221 """Find the web server for this host on the LIGO Data Grid.
222
223 @returns the fully-qualified domain name of the web server
224 associated with this host.
225
226 Example:
227 >>> socket.getfqdn()
228 ldas-pcdev1.ligo-la.caltech.edu
229 >>> htmlutils.get_web_server()
230 "https://ldas-jobs.ligo-la.caltech.edu"
231 """
232
233 fqdn = socket.getfqdn()
234
235
236 if re.search("ligo.caltech", fqdn):
237 url = "https://ldas-jobs.ligo.caltech.edu"
238 elif re.search("ligo-wa", fqdn):
239 url = "https://ldas-jobs.ligo-wa.caltech.edu"
240 elif re.search("ligo-la", fqdn):
241 url = "https://ldas-jobs.ligo-la.caltech.edu"
242 elif re.search("atlas", fqdn):
243 match = re.search("atlas[13]", fqdn)
244 if match:
245 host = match.group()
246 url = "https://%s.atlas.aei.uni-hannover.de" % host
247 else:
248 url = "https://atlas1.atlas.aei.uni-hannover.de"
249 elif re.search("phy.syr", fqdn):
250 url = "https://sugar-jobs.phy.syr.edu"
251 elif re.search("astro.cf", fqdn):
252 url = "https://gravity.astro.cf.ac.uk"
253 elif re.search("phys.uwm", fqdn):
254 url = "https://ldas-jobs.phys.uwm.edu"
255 else:
256 raise socket.herror("No web domain known for host %s." % fqdn)
257
258 return url
259
260
261
262
263
264 -def simple_page(header, htag="h1", desc=None, plotlist=None,
265 args=None, version=None, classdict={}, init=False):
266 """
267 Returns a glue.markup.page object with an h1 heading, descriptive text,\
268 some plots, and the arguments used to generate it.
269
270 Designed for embedding/including as a frame in a larger page.
271
272 Arguments:
273
274 header : str
275 text to write as header (<h1>)
276 htag : str
277 HTML tag to use for header, default <h1>
278 desc : str
279 descriptive text to print below header(s) (<p>)
280 plotlist : list
281 list of filepath strings or (path, title) tuples to include
282 (<a>,<img>)
283 args : [ str | list ]
284 string with arguments used to generate plots, or list of arguments
285 to be space-separated
286 version : str
287 code version str/number to include
288 classdict : dict
289 dict containing HTML class strings for each tag used
290 init : [ True | False ]
291 initialise the markup.page object, adds HTML and BODY tags
292 """
293
294
295 page = markup.page()
296
297
298 if init: page.init()
299
300
301 if header is not None:
302 getattr(page, htag)(header, class_=classdict.get(htag,""),\
303 id_="%s_simple-page" % htag,
304 onclick="toggleVisible();")
305
306
307 hclass = classdict.get(htag, "open")
308 if hclass == "closed": display="none"
309 else: display="block"
310 page.div(class_=classdict.get("div",""), id_="div_simple-page",\
311 style="display: %s;" % display)
312
313 if desc is not None:
314 page.p(desc, class_=classdict.get("p",""))
315
316
317 if plotlist is not None:
318 for p in plotlist:
319 if isinstance(p, str):
320 alt = None
321 else:
322 p,alt = p
323 page.a(href=p, title=alt, class_=classdict.get("a",""))
324 page.img(src=p, alt=alt, class_=classdict.get("img",""))
325 page.a.close()
326
327 if args is not None:
328 page.p("Generated by running:", class_=classdict.get("p",""))
329 if not isinstance(args, str): args = " ".join(args)
330 page.pre(args, class_=classdict.get("pre",""))
331 if version is not None:
332 page.p("Code version: %s" % version, class_=classdict.get("p",""))
333
334 page.div.close()
335
336 return page
337
338
339
340
341
342 -def build_page(icon=None, banner=None, homebutton=None, tabs=None,\
343 menu=None, frame=None, **initargs):
344 """
345 Build a complete HTML page from 6 components: icon banner homebutton tabs
346 menu frame. All other args passed to glue.markup.page.init function.
347 See docstring of that function for help.
348
349 Body is built to the following format:
350
351 <div id_="container">
352 <div class="content" id_="header">
353 <div>
354 <div class="nav">
355 ICON
356 </div>
357 <div class="frame">
358 BANNER
359 </div>
360 </div>
361 </div>
362 <div class="content" id_="tabs">
363 <div>
364 <div class="nav">
365 HOMEBUTTON
366 </div>
367 <div class="frame">
368 TABS
369 </div>
370 </div>
371 </div>
372 <div class="content" id_="main">
373 <div>
374 <div class="nav">
375 MENUBAR
376 </div>
377 <div class="frame">
378 FRAME
379 </div>
380 </div>
381 </div>
382 </div>
383
384 """
385
386
387 page = markup.page()
388 initargs.setdefault("doctype", "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">")
389 page.init(**initargs)
390
391 page.div(id_="container")
392
393
394 if icon is not None or banner is not None:
395 page.div(class_="content", id_="header")
396 page.div()
397 page.div(str(icon), class_="nav", id_="headernav")
398 page.div(str(banner), class_="frame", id_="headerframe")
399 page.div.close()
400 page.div.close()
401
402
403 if homebutton is not None or tabs is not None:
404 page.div(class_="content", id_="tabs")
405 page.div()
406 if not homebutton: homebutton = ""
407 page.div(str(homebutton), class_="nav", id_="tabsnav")
408 page.div(str(tabs), class_="frame", id_="tabsframe")
409 page.div.close()
410 page.div.close()
411
412
413 if menu is not None or frame is not None:
414 page.div(class_="content", id_="main")
415 page.div()
416 page.div(str(menu), class_="nav", id_="mainnav")
417 page.div(str(frame), class_="frame", id_="mainframe")
418 page.div.close()
419 page.div.close()
420
421 page.div.close()
422
423 return page
424
425
426
427
428
429 -def summary_page(header=None, htag="h1", desc=None, plotlist=None,
430 text=None, subplots=None, info=None, classdict={},\
431 init=False):
432 """
433 Returns a glue.markup.page object with an heading, descriptive text,\
434 summary section with one plot and text, section with other plots,
435 section with subplots, then section with info.
436
437 Designed for embedding/including as a frame in a larger page.
438
439 Keyword arguments:
440
441 header : str
442 text to write as header (<h1>)
443 htag : str
444 HTML tag to use for header, default <h1>
445 desc : str
446 descriptive text to print below header(s) (<p>)
447 plotlist : list
448 list of filepath strings or (path, title) tuples to include
449 (<a>,<img>)
450 text : [ str | glue.markup.page ]
451 text to print below summary plot (<p>), or glue.markup.page to
452 drop in with no enclosing tags.
453 subplotlist : list
454 list of filepath strings or (path, title) tuples to include
455 (<a>,<img>)
456 info : str
457 information to print below summary plot (<p>), or
458 glue.markup.page to drop in with no enclosing tags.
459 classdict : dict
460 dict containing HTML class strings for each tag used
461 init : [ True | False ]
462 initialise the markup.page object, adds HTML and BODY tags
463 """
464
465
466 page = markup.page()
467
468
469 if init: page.init()
470
471
472 if header is not None:
473 getattr(page, htag)(header, class_=classdict.get(htag,""),\
474 id_="%s_simple-page" % htag,
475 onclick="toggleVisible();")
476
477
478 hclass = classdict.get(htag, "open")
479 if hclass == "closed": display="none"
480 else: display="block"
481 page.div(class_=classdict.get("div"), id_="div_simple-page",\
482 style="display: %s;" % display)
483
484
485 if desc is not None:
486 page.p(desc, class_=classdict.get("p",""))
487
488
489 if plotlist is not None:
490 Np = len(plotlist)
491 if Np:
492 p = plotlist[0]
493 if isinstance(p, str):
494 alt = None
495 else:
496 p,alt = p
497 page.a(href=p, title=alt, class_=classdict.get("a",""))
498 page.img(src=p, alt=alt, class_=classdict.get("img",""))
499 page.a.close()
500
501
502 if text is not None:
503 if isinstance(markup.page):
504 page.add(text())
505 else:
506 page.p(str(text), class_=classdict.get("p",""))
507
508
509 if plotlist is not None and Np > 1:
510 for p in plotlist[1:]:
511 if isinstance(p, str):
512 alt = None
513 else:
514 p,alt = p
515 page.a(href=p, title=alt, class_=classdict.get("a",""))
516 page.img(src=p, alt=alt, class_=classdict.get("img",""))
517 page.a.close()
518
519 if info is not None:
520 if isinstance(markup.page):
521 page.add(info())
522 else:
523 page.p(str(info), class_=classdict.get("p",""))
524
525 page.div.close()
526
527
528
529
530
531 -def about_page(executable, cmdargs, version=False, filedict={},\
532 classdict={"h2":"open", "p":"line", "div":"about"}, init=False):
533 """
534 Returns a glue.markup.page object formatting the given executable,\
535 commandline arguments, and any included files.
536
537 Arguments:
538
539 executable : string
540 path of executable file (sys.argv[0])
541 cmdargs : iterable
542 set of command line arguments (sys.argv[1:])
543
544 Keyword arguments:
545
546 filedict : [ dict | iterable ]
547 iterable of ("name", filepath) pairs to insert in full into the page
548 classdict : dict
549 dict containing HTML class strings for each tag used
550 init : [ True | False ]
551 initialise the markup.page object, adds HTML and BODY tags
552 """
553 page = markup.page()
554
555
556 if init: page.init()
557
558 page.h1("About", class_=classdict.get("h1",None))
559 page.p("This page was generated with %s using the following tools."\
560 % (executable), class_=classdict.get("p",None))
561
562 def pre(h2, content, id_=0):
563 page.h2(h2, id_="h2_%s" % id_, onclick="toggleVisible();",\
564 class_=classdict.get("h2",None))
565 page.div(id_="div_%s" % id_, style="display: block;",\
566 class_=classdict.get("div",None))
567 page.pre(content, class_=classdict.get("pre",None))
568 page.div.close()
569
570 i = 0
571
572
573 pre("Command line arguments", " ".join([executable]+cmdargs), id_=i)
574 i += 1
575
576
577 if version:
578 pre("Version", version, id_=i)
579 i += 1
580
581 if isinstance(filedict, dict):
582 filedict = filedict.iteritems()
583
584 for name,path in filedict:
585 pre(name, open(path, "r").read(), id_=i)
586 i += 1
587
588 return page
589
590
591
592
593
595 """Find the web readable directory for the user on thie host.
596
597 @returns the absolute path of the "public_html" equivalent
598 directory on this host.
599
600 Example:
601 >>> socket.getfqdn()
602 ldas-pcdev1.ligo-la.caltech.edu
603 >>> htmlutils.get_html_directory()
604 "/home/duncan.macleod/public_html"
605 """
606
607 fqdn = socket.getfqdn()
608
609 if sys.platform == "darwin":
610 public_html = "Sites"
611 elif re.search("atlas", fqdn):
612 public_html = "WWW/LSC"
613 else:
614 public_html = "public_html"
615
616
617 home = os.path.expanduser("~")
618 userdir = os.path.join(home, public_html)
619 if not os.path.isdir(userdir):
620 raise OSError("Web UserDir %s not found." % userdir)
621
622 return userdir
623