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