packages feed

Hayoo (empty) → 1.2.0

raw patch · 42 files changed

+9908/−0 lines, 42 filesdep +Holumbus-Searchenginedep +MonadCatchIO-transformersdep +basesetup-changedbinary-added

Dependencies added: Holumbus-Searchengine, MonadCatchIO-transformers, base, binary, bytestring, bzlib, containers, deepseq, enummapset, filepath, hack, hack-contrib, hack-handler-simpleserver, hslogger, hxt, hxt-cache, hxt-charproperties, hxt-curl, hxt-http, hxt-regex-xmlschema, hxt-unicode, hxt-xpath, json, mtl, network, old-time, parsec, process, snap, snap-core, snap-server, tar, text, transformers, xhtml-combinators, zlib

Files

+ Hayoo.cabal view
@@ -0,0 +1,181 @@+name:          Hayoo+version:       1.2.0+license:       MIT+license-file:  LICENSE+author:        Sebastian M. Schlatt, Timo B. Huebel, Uwe Schmidt+copyright:     Copyright (c) 2007 - 2012 Uwe Schmidt, Sebastian M. Schlatt and Timo B. Kranz+maintainer:    Timo B. Huebel <tbh@holumbus.org>+stability:     experimental+category:      Text, Data+synopsis:      The Hayoo! search engine for Haskell API search on hackage+homepage:      http://holumbus.fh-wedel.de+description:   The Hayoo! search engine based no the Holumbus framework provides a document indexer+               and a crawler to build indexes over Haddock generated API documentation and packages+               as well as a query interface for these indexes.+cabal-version: >=1.6+build-type:    Simple+-- tested-with:  ghc-7.0.3++extra-source-files:+    README+    index/hayoo/hayoo.css+    index/hayoo/fhw.gif+    index/hayoo/loader.gif+    index/hayoo/favicon.ico+    index/hayoo/content.js+    index/hayoo/hayoo.js+    index/hayoo/prototype.js+    index/hayoo/ft.png+    index/hayoo/hayoo.png+    index/hayoo/hol.png+    index/hayoo/holumbus.png+    index/hayoo/minus.png+    index/hayoo/plus.png+    index/hayoo/opensearch.xml++-- ------------------------------------------------------------++executable hayooIndexer+  main-is:      HayooIndexer.hs++  other-modules:  Hayoo.PackageInfo+                , Hayoo.URIConfig+                , Hayoo.PackageArchive+                , Hayoo.IndexTypes+                , Hayoo.Haddock+                , Hayoo.HackagePackage+                , Hayoo.IndexConfig++  build-depends:  base                   >= 4   && < 5+                , Holumbus-Searchengine  >= 1.2 && < 2+                , binary                 >= 0.5 && < 1+                , bytestring             >= 0.9 && < 1+                , bzlib                  >= 0.4 && < 1+                , containers             >= 0.2 && < 1+                , deepseq                >= 1.1 && < 2+                , enummapset             >= 0   && < 1+                , filepath               >= 1   && < 2+                , hxt                    >= 9.1 && < 10+                , hxt-cache              >= 9   && < 10+                , hxt-charproperties     >= 9   && < 10+                , hxt-curl               >= 9   && < 10+                , hxt-http               >= 9   && < 10+                , hxt-regex-xmlschema    >= 9   && < 10+                , hxt-xpath              >= 9.1 && < 10+                , network                >= 2.1 && < 3+                , old-time               >= 1   && < 2+                , parsec                 >= 2.1 && < 4+                , process                >= 1   && < 2+                , tar                    >= 0.3 && < 1+                , zlib                   >= 0.5 && < 1 ++  hs-source-dirs: src++  if impl( ghc >= 7 )+    ghc-options: -threaded -Wall -funbox-strict-fields -fwarn-tabs -rtsopts+  else+    ghc-options: -threaded -Wall -funbox-strict-fields++  extensions:++-- ------------------------------------------------------------++executable hayooSearch+  main-is:      HayooSearch.hs++  other-modules:  Hayoo.FunctionInfo+                , Hayoo.IndexTypes+                , Hayoo.PackageInfo+                , Hayoo.PackageRank+                , Hayoo.Search.Application+                , Hayoo.Search.Common+                , Hayoo.Search.EvalSearch+                , Hayoo.Search.HTML+                , Hayoo.Search.JSON+                , Hayoo.Search.Pages.Static+                , Hayoo.Search.Pages.Template+                , Hayoo.Search.Parser+                , Hayoo.Signature++  build-depends:  base                      >= 4   && < 5+                , Holumbus-Searchengine     >= 1   && < 2+                , hack                      >= 2009.10.30+                , hack-contrib              >= 2010.9.28+                , hslogger                  >= 1   && < 2+                , hxt-unicode               >= 9   && < 10+                , json                      >= 0.4 && < 1+                , text                      >= 0.7 && < 1+                , xhtml-combinators         >= 0.2 && < 1++  if impl( ghc >= 7.0.3 )+    build-depends: hack-handler-simpleserver >= 0.2.0.2 && < 1+  else+    build-depends: hack-handler-simpleserver >= 0.2.0.1++  hs-source-dirs: src++  if impl( ghc >= 7 )+    ghc-options: -threaded -Wall -funbox-strict-fields -fwarn-tabs -rtsopts+  else+    ghc-options: -threaded -Wall -funbox-strict-fields++  extensions:++-- ------------------------------------------------------------++Flag hayooSnap+  Description: Build Hayoo! search with Snap server+  Default: True++executable hayooSnap+ -- if flag(hayooSnap)+  main-is:      HayooSnap.hs++  other-modules:  Hayoo.FunctionInfo+                , Hayoo.IndexTypes+                , Hayoo.PackageInfo+                , Hayoo.PackageRank+                , Hayoo.Search.Common+                , Hayoo.Search.EvalSearch+                , Hayoo.Search.HTML+                , Hayoo.Search.JSON+                , Hayoo.Search.Pages.Static+                , Hayoo.Search.Pages.Template+                , Hayoo.Search.Parser+                , Hayoo.Signature+                , Hayoo.Snap.Application+                , Hayoo.Snap.Extension.HayooState+                , Hayoo.Snap.Site++  build-depends:  base                      >= 4     && < 5+                , bytestring                >= 0.9.1 && < 0.10+                , Holumbus-Searchengine     >= 1.2   && < 2+                , hslogger                  >= 1     && < 2+                , hxt-unicode               >= 9     && < 10+                , json                      >= 0.4   && < 1+                , MonadCatchIO-transformers >= 0.2.1 && < 0.3+                , mtl                       >= 2     && < 3+                , snap                      == 0.4.*+                , snap-core                 == 0.4.*+                , snap-server               == 0.4.*+                , text                      >= 0.11  && < 1+                , transformers              >= 0.2.2 && < 0.3+                , xhtml-combinators         >= 0.2   && < 1+ -- else+  -- hayooSnap is currently only tested with ghc-7.0.3, with ghc-6.12 you will run into cabal package hell+  -- with ghc-6.12 there is a mess with hack-handler-simpleserver, which wants old versions of network, web-encodings and text+  -- so this is a dummy+  -- main-is:      Dummy.hs++ hs-source-dirs: src++ if impl( ghc >= 7 )+  ghc-options: -threaded -Wall -funbox-strict-fields -fwarn-tabs -rtsopts+               -fno-warn-orphans -fno-warn-unused-do-bind+ else+  ghc-options: -threaded -Wall -funbox-strict-fields -fwarn-tabs+               -fno-warn-orphans -fno-warn-unused-do-bind++ extensions: TypeSynonymInstances MultiParamTypeClasses++-- ------------------------------------------------------------
+ LICENSE view
@@ -0,0 +1,21 @@+The MIT License++Copyright (c) 2007 Sebastian M. Schlatt, Timo B. Hübel++Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the "Software"),+to deal in the Software without restriction, including without limitation the+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.
+ README view
@@ -0,0 +1,19 @@+The Hayoo! Search Engine+========================++The system is build with the familiar 3 steps++cabal configure+cabal build+cabal install++There are 3 executables: hayooIndexer, hayooSearch and hayooFastCGI,+which will be installed under "~/.cabal/bin".++The applications require besides various hackage packages+the Holumbus-Searchengine package. The sources for this package+are located in the repository "../Holumbus-Searchengine".++All sources are located in subdir "src", the applications+can be executed in subdir "index". There is a Makefile to+build the hackage index and to start the Hayoo seach server.
+ Setup.lhs view
@@ -0,0 +1,8 @@+#!/usr/bin/runhaskell++> module Main where++> import Distribution.Simple++> main :: IO ()+> main = defaultMain
+ index/hayoo/content.js view
@@ -0,0 +1,123 @@++help = ' \+<div id="result"> \+  <div id="status">Enter some search terms above to start a search.</div> \+\+  <div id="helptext" class="text"> \+\+    <h2>Basic Usage</h2> \+    <p> \+      By default, Hayoo! searches for function names, module names, signatures and function descriptions. \+      With every letter typed, Hayoo! will show the results it thinks are best matching the query as \+      well as some suggestions on how the words from the query could be completed. Clicking one of these \+      suggestions will replace the according word in the query. \+    </p> \+    <p> \+      Hayoo! displays results as a list of functions, including full qualified module name and the function \+      signature. Clicking the function name will lead directly to the corresponding documentation while \+      clicking the module name will lead to the documentation of the module. Additionally, Hayoo! shows \+      a little bit of the function description (if available) and provides a link leading directly to \+      the source of the function (if available). \+    </p> \+\+    <h2>Advanced Queries</h2> \+    <p> \+      If words are seperated by whitespace, Hayoo! will search for results containing both words. Instead of \+      using whitespace, the explicit <span class="query">AND</span> operator can be used. Hayoo! also  \+      supports <span class="query">OR</span> and <span class="query">NOT</span> operators, \+      although the <span class="query">NOT</span> operator may only be used together with \+      <span class="query">AND</span>, e.g. <span class="query">map NOT fold</span> or \+      <span class="query">map AND NOT fold</span>. Operator precedence can be influenced using round parentheses. \+      Phrases can be searched using double quotes, e.g. <span class="query">"this is a phrase"</span>. \+    </p> \+    <p> \+      It is possible to restrict a search to certain packages or modules. The most simple way would be to just include \+      the package name in the search, e.g. <span class="query">map base</span> will prefer hits from the base package. \+      But the restriction can also be more explicit, like <span class="query">map package:base</span> or like \+      <span class="query">map module:data.list</span>. It is also possible to specify several different modules or packages, \+      like this: <span class="query">fold module:(data.list OR data.map)</span>. This will return all hits for fold in \+      the module hierarchies below Data.List and Data.Map. \+    </p> \+    <p> \+      Hayoo! always performs fuzzy queries. This means, it tries to find something even if the query contains \+      spelling	errors. For example, Hayoo! will still find "fold" if "fodl" is searched. If Hayoo! \+      detects "-&gt;" in the query string, it will only search for signatures. A signature query may consist \+      of explicit type names as well as type variables. For example, searching for "a -&gt; b" will find \+      signatures like "Int -&gt; Bool". \+    <p> \+\+    <h2>Scope</h2> \+    <p> \+      Currently, Hayoo! searches all packages available on <a href="http://hackage.haskell.org">Hackage</a> \+      and <a href="http://www.haskell.org/gtk2hs/">Gtk2Hs</a>. Additionally, any Haskell documentation generated \+      by Haddock can be included in Hayoo!. \+      Just send a message including an URI where the documentation can be found to \+      <a href="mailto:hayoo@holumbus.org">hayoo@holumbus.org</a>. \+    </p> \+  </div> \+</div> \+';++about = ' \+<div id="result"> \+  <div id="status">Enter some search terms above to start a search.</div> \+\+  <div id="abouttext" class="text"> \+\+    <h2>About Hayoo!</h2> \+    <p> \+      Hayoo! is a search engine specialized on <a href="http://www.haskell.org">Haskell</a> API documentation. The goal of \+      Hayoo! is to provide an interactive, easy-to-use search interface to the documenation of various Haskell packages and \+      libraries. Although the Hayoo! data is regularly updated, we might miss a package or library. If you think there \+      is some documentation for Haskell modules available on the Internet which should be added to Hayoo!, \+      just drop us a note at <a href="mailto:hayoo@holumbus.org">hayoo@holumbus.org</a> and tell us the location where \+      we can find the documentation. \+    </p> \+\+    <h2>Background</h2> \+    <p> \+      Hayoo! is an example application of the <a href="http://holumbus.fh-wedel.de">Holumbus</a> framework and was  \+      heavily inspired by <a href="http://www.haskell.org/hoogle">Hoogle</a>. The Holumbus library \+      provides the search and indexing backend for Hayoo!. Holumbus and Hayoo! have been developed by  \+      Sebastian M. Schlatt and Timo B. Hübel at <a href="http://www.fh-wedel.de">FH Wedel University of Applied Sciences</a>.  \+      The Holumbus framework provides the basic building blocks for creating highly customizable search engines.  \+      To demonstrate the flexibility of the framework by a very special use case, the Hayoo! Haskell API search  \+      was implemented using Holumbus. \+    </p> \+    <p> \+      Currently, Hayoo! is still in beta stage. This means, it can become unavailable unexpectedly, as we \+      do some maintenance or add new features. Therefore you should not yet rely on Hayoo! as primary \+      search engine for Haskell documentation. \+    </p> \+\+    <h2>Technical Information</h2> \+    <p> \+      Hayoo! is entirely written in Haskell, including the underlying webserver. The web interface uses the \+      <a href="http://darcs2.fh-wedel.de/repos/janus">Janus</a> application server, written by Christian Uhlig. \+      This server allows easy and seamless integration of Haskell code in web applications. \+    </p> \+\+    <h2>Feedback</h2> \+    <p> \+      We would like to know what you think about Hayoo!, therefore you can reach us at \+      <a href="mailto:hayoo@holumbus.org">hayoo@holumbus.org</a> and tell us about bugs, suggestions or \+      anything else related to Hayoo!.\+    </p> \+\+    <div id="hol"> \+      <a href="http://holumbus.fh-wedel.de"><img class="logo" src="hayoo/hol.png" alt="Holumbus logo"/></a> \+    </div> \+    <div id="fhw"> \+      <a href="http://www.fh-wedel.de"><img class="logo" src="hayoo/fhw.gif" alt="FH-Wedel logo"/></a> \+    </div> \+  </div> \+</div> \+';++function showHelp() {+  $("result").replace(help);+}++function showAbout() {+  $("result").replace(about);+}
+ index/hayoo/favicon.ico view

binary file changed (absent → 1150 bytes)

+ index/hayoo/fhw.gif view

binary file changed (absent → 3744 bytes)

+ index/hayoo/ft.png view

binary file changed (absent → 9440 bytes)

+ index/hayoo/hayoo.css view
@@ -0,0 +1,491 @@+html {+	height:100%;+}++body {+	font-family:Tahoma,Verdana,sans-serif;+	margin:0px;+	font-size:11px;+	height:100%;+}++table, tr, td {+	font-family:Tahoma,Verdana,sans-serif;+	font-size:11px;+  margin:0;+  padding:0;+}++p {+  margin:0;+}++a {+	font-weight:bold;+	color:gray;+}+a:link {+	text-decoration:none;+}+a:hover {+	text-decoration:none;+	color:silver;+}+a:visited {+	text-decoration:none;+}+a:active {+	text-decoration:none;+}++img.logo { border: none; }++span.query {+	font-style: italic;+}++div.clear {+  clear:both !important;+}++#throbber { +	vertical-align:text-bottom;+}++#copyright .author {+	font-weight: bold;+}++#info {+	float:right;+	color:gray;+	padding:5px;+}++#shutter {+	background-color: #000000;+	height: 100%;+	width: 100%;+	left: 0pt;+	top: 0pt;+	position: fixed;+	opacity:0.5;+	z-index:1000;+}++.text {+	color:gray;+	padding:20px;+}++.box {+	margin-left: auto;+	margin-right: auto;+	margin-bottom: auto;+	margin-top: 71px;+	background-color: white;+	visibility: visible;+	opacity:1.0;+	width: 720px;+	height: 540px;+	padding:10px;+	z-index:1004;+	color:gray;+	text-align:left;+	padding-left:20px;+	padding-right:20px;+}++#examples {+  width:80%;+  margin-left:auto;+  margin-right:auto;+  background-color:#F4F4F4;+  border:1px solid silver;+  padding:20px;+}++.example a {+	font-weight: normal;+	color: #DD0000;+	text-decoration: underline;+}++#container {+	min-height:100%;+	position:relative;+}++#sponsors {+	margin-top:20px;+	width: 100%;+	text-align:center;+}++#fhw {+	display: inline;+}++#hol {+	display: inline;+}++#ft {+	margin: 0px 50px;+	display: inline;+}++#result {+	padding-bottom:80px;+}++#credits {+	border-top:1px solid silver;+	border-bottom:1px solid silver;+	margin-top:10px;+	margin-bottom:10px;+	color:gray;+	padding-top:10px;+	padding-bottom:5px;+	background-color:#F4F4F4;+	bottom:0;+	position:absolute;+	width:100%;+}++#powered {+	margin-left:10px;+	float:left;+	vertical-align: middle;+}++#libs {+	float: left;+	padding-top: 10px;+	margin-right:5px;+}++#authors {+	text-align:right;+	margin-right:10px;+	float:right;+}++#feedback {+	margin-bottom: 5px;+}++#logo {+	float:left;+}++#status {+	border-top:1px solid silver;+	border-bottom:1px solid silver;+	background-color:#F4F4F4;+	clear:both;+	color:gray;+	font-weight:bold;+	vertical-align:middle;+	padding:2px;+	padding-left:5px;+}++#aggregation {+  margin:20px;+	float:right;+	background-color:#F4F4F4;+	border:1px solid silver;+}++div.headline {+  color:gray;+  font-weight:bold;+  margin-bottom:5px;+}++#modules {+	padding:10px;+}++#packages {+	padding:10px;+}++#hackage {+  float:left;+  margin:20px;+	background-color:#F4F4F4;+	border:1px solid silver;+  width:350px;+  padding:10px;+}++#hackage a {+  font-weight:normal+}++#hackage a:hover {+  text-decoration:underline;+}++#hackage div.package {+  margin-bottom:10px;+}++#hackage a.category {+  color:#DD0000;+}++#hackage a.name {+  font-weight:bold;+  color:#0700C3;+}+  +#hackage div.author {+  color:#25CA00;+  font-weight:bold;+  font-size:9px; +}++#hackage span.synopsis {+  color:gray;+	font-weight:bold;+}++#hackage div.description {+  font-size:9px;+  color:gray;+}++#words {+	padding-left: 10px;+	padding-right: 10px;+	margin-top: 25px;+	margin-bottom: 5px;+	text-align:center;+	word-spacing: 1.5em;+}++a.rootModuleName {+	color:#DD0000;+  font-weight:normal;+}++.rootModuleCount {+	color:grey;+}++a.packageLink {+	color:#25CA00;+  font-weight:normal;+}++.packageCount {+	color:grey;+}++#documents {+	padding:10px;+}++tr.details td {+  padding-bottom:15px;+}++td.module {+	text-align: right;+	vertical-align: top;+}++td.description > div {+  max-height:1.4em;+  overflow:hidden;+  color: gray;+  text-align: left;+  float:left;+  margin-left:5px;+  width:100%;+}++td.description > div.unfold {+	max-height:none;+}++td.description div.description {+  overflow-x:auto;+  float:left;+  max-width:80%;+}++td.description pre {+  white-space:pre-wrap;+}++td.signature {+  color: black;+  margin-left:5px;+  width:100%;+}++td.package {+	text-align: right;+	vertical-align: top;+}++td.name {+  vertical-align:top;+  width:auto;+}++span.source {+  margin-left:5px;+}++a.module {+	font-weight: normal;+	color: #DD0000;+	text-decoration: none;+}++a.module:hover {+	text-decoration: underline;+}++a.function {+	color: #0700C3;+	font-weight: bold;+}++a.function:hover {+	text-decoration: underline;+}++a.package {+	color: #25CA00;+	font-weight: bold;+}++a.package:hover {+	text-decoration: underline;+}++a.source {+	color: #F0DE00;+	font-weight: bold;+}++a.source:hover {+	text-decoration: underline;+}++.unfold a.toggleFold {+  background-position:-10px;+  background:url(minus.png);+}++a.toggleFold {+	float:left;+	margin-right:3px;+	cursor:pointer;+  display:block;+  width:10px;+  height:10px;+  background:url(plus.png);+  margin-top:2px;+}++#queryinterface {+	height:25px;+	padding-top:30px;+	padding-left:300px;+}++#query {+	color:gray;+	margin:0px;+}++#querytext {+	margin-top:5px;+	border-color:silver;+	border-width:1px;+	border-style:solid;+	width:300px;+}++#querybutton {+	margin-top:5px;+	border-color:silver;+	border-width:1px;+	border-style:solid;+	background-color:#F4F4F4;+	margin-left:5px;+	margin-right:5px;+}++#pager {+	text-align:center;+	width:100%;+	margin-top:5px;+	margin-bottom:5px;+}++a.page {+	padding-left:5px;+	padding-right:5px;+}++.current {+	font-weight:bold;+	color:silver;+	padding-left:5px;+	padding-right:5px;+}++a.next {+	padding-left:5px;+	padding-right:5px;+}++a.previous {+	padding-left:5px;+	padding-right:5px;+}++a.cloud {+	font-weight:normal;+}++.cloud1 { font-size:10px; }+.cloud2 { font-size:13px; }+.cloud3 { font-size:16px; }+.cloud4 { font-size:19px; }+.cloud5 { font-size:22px; }+.cloud6 { font-size:25px; }+.cloud7 { font-size:28px; }+.cloud8 { font-size:31px; }+.cloud9 { font-size:33px; }++.link { color:navy; }+.link:hover { +	color:navy; +	text-decoration:underline;+}++.title { font-size:10pt; }+.contexts { color:gray; }+.context { font-weight:bold; }+.uri { color:green; }+.declaration { font-style: italic; }++div.stats {+  font-size:80%;+  font-style:italic;+}++.example p {+  margin:10px 0px;+}++
+ index/hayoo/hayoo.js view
@@ -0,0 +1,136 @@+/*++  The Hayoo! AJAX interface++  Copyright  : Copyright (C) 2008 Timo B. Huebel+  License    : MIT++  Maintainer : Timo B. Huebel (tbh@holumbus.org)+  Version    : 0.2++*/++prevInput = "";+lastLocation = window.location.hash;+refreshRequired = false;++Event.observe(window, 'load', focusQuery);++function focusQuery() {+  $("querytext").focus();+}++/*+Event.observe(window, 'load', checkForInitialQuery);++function checkForInitialQuery() {+  var argument = window.location.search.gsub(/\+/, '%20').toQueryParams()['query'];++  if (argument) {+    $("querytext").value = argument;+    processQuery(0);+  }+}+*/++function checkForQuery () {+  if (lastLocation != window.location.hash) {+    lastLocation = window.location.hash;+    var prev = window.location.hash.split(":");++    var start = prev[0].substr(1);+    prev.shift();+    var query = decodeURIComponent(prev.join(":"));++    $("querytext").value = query;+    processQuery(parseInt(start));+  }+  else {+    window.setTimeout(checkForQuery, 200);+  }+}++function tryProcessQuery () {+  var query = $("querytext").value;+  window.setTimeout('checkProcessQuery(\'' + encodeURIComponent(query) + '\')', 300);+}++function checkProcessQuery (query) {+  if ((query != prevInput) && (query == encodeURIComponent($("querytext").value))) {+    prevInput = query;+    processQuery(0);+  }+}++function forceProcessQuery () {+  processQuery(0);+  return false;+}++function processQuery (start) {+  var query = $("querytext").value;+  if (query.length > 0) {+    $("throbber").show();+    refreshRequired = true;+    new Ajax.Request("hayoo.html?query=" + encodeURIComponent(query) + "&start=" + start + "&static=False",+      {+        method:'get',+        onSuccess: function(transport) {+          lastXMLResult = transport.responseXML;+          lastTXTResult = transport.responseText;+          lastQuery = query;+          window.location.hash = start + ":" + encodeURIComponent(query);+          lastLocation = window.location.hash;+          displayResult(transport.responseText, query);+          checkForQuery();+          piwikTracker.trackPageView("hayoo.html?query=" + encodeURIComponent(query));+        },+        onFailure: function() {+          var resultError = "<div id=\"result\"><div id=\"status\">Error: Could not execute query.</div><div id=\"words\">&nbsp;</div><div id=\"documents\">&nbsp;</div><div id=\"pager\">&nbsp;</div></div>";+          $("result").replace(resultError);+        }+      }+    );+  }+}++function displayResult (result, query) {+  if (refreshRequired) {+    refreshRequired = false;+    $("result").replace(result);+    $("querytext").defaultValue = query;+  }+  $("throbber").hide();+}++function replaceInQuery (needle, substitute) {+  if ($("querytext").value == "") {+    $("querytext").value = substitute;+  } else {+    $("querytext").value = $("querytext").value.gsub(needle, substitute);+  }+  processQuery(0);+}++function addToQuery (addition) {+  $("querytext").value = $("querytext").value + ' ' + addition;+  processQuery(0);+}++function toggleFold(node) {++  if ($(node).parentNode.hasClassName('unfold')) {+    $(node).parentNode.removeClassName('unfold');+  } else {+    $(node).parentNode.addClassName('unfold');+  }++  return false;+}++function showPage (page) {+  if ($("querytext").value == "") {+    $("querytext").value = lastQuery;+  }+  processQuery (page);+}
+ index/hayoo/hayoo.png view

binary file changed (absent → 9735 bytes)

+ index/hayoo/hol.png view

binary file changed (absent → 11692 bytes)

+ index/hayoo/holumbus.png view

binary file changed (absent → 6230 bytes)

+ index/hayoo/loader.gif view

binary file changed (absent → 1849 bytes)

+ index/hayoo/minus.png view

binary file changed (absent → 178 bytes)

+ index/hayoo/opensearch.xml view
@@ -0,0 +1,10 @@+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">+  <ShortName>Hayoo!</ShortName>+  <Description>Haykell API Search</Description>+  <InputEncoding>UTF-8</InputEncoding>+  <Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwDDAAd2wwAH3MMAB%2FzDAAfrwwAHgf%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwDDAAd%2FwwAHyMMAB9DDAAfNwwAHecMAB07DAAf%2FwwAHa%2F%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAMMAB1XDAAf%2FwwAHav%2F%2F%2FwDDAAdIwwAH%2F8MAB1QAAN0BAADdwQAA3aL%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwDDAAcswwAH%2F8MABy3%2F%2F%2F8AwwAHSMMAB%2F%2FDAAdGAADdBwAA3ecAAN3C%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8AwwAHXMMAB%2FbDAAcD%2F%2F%2F%2FAMMAB0jDAAf%2FwwAHOP%2F%2F%2FwAAAN0IAADdCf%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAMMAB4zDAAfH%2F%2F%2F%2FAMMAByLDAAeEwwAH%2F8MABy7%2F%2F%2F8A%2F%2F%2F%2FAAAA3Uz%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwDDAAfBwwAH58MAB7fDAAeVwwAHe8MAB%2F%2FDAAdq%2F%2F%2F%2FAP%2F%2F%2FwAAAN3AAADdCf%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwDDAAc8wwAH%2F8MAB1v%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwDDAAfgwwAHv%2F%2F%2F%2FwD%2F%2F%2F8AAADdsQAA3U3%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwDDAAcCwwAHzcMAB9DDAAcB%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8AwwAHi8MAB%2F3DAAcY%2F%2F%2F%2FAAAA3ZcAAN2c%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8AwwAHZMMAB%2F%2FDAAdI%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAMMABzXDAAf%2FwwAHav%2F%2F%2FwAAAN19AADd6QAA3QL%2F%2F%2F8AwwAHD8MAB%2BnDAAe%2F%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwDDAAcUwwAH%2F8MAB8P%2F%2F%2F8AAADdZAAA3f8AAN05%2F%2F%2F%2FAMMAB5XDAAf%2FwwAHWf%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwDDAAd9wwAHysMAB%2BDDAAfgwwAHszcAoF8AAN3%2FAADdiMMAB63DAAfQwwAH0MMAB8LDAAd3%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwAAAN0pAADd%2FgAA3a7%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAAAA3UUAAN0d%2F%2F8AAP%2F%2FAAD%2FDwAA8b8AAPuzAAD7swAA%2B78AAPM%2FAADwuwAA95sAAOeZAADv3QAAz80AAJ%2BEAAAP%2FAAA%2F%2F8AAA%3D%3D</Image>+  <Url type="text/html" method="GET" template="http://www.holumbus.org/hayoo/hayoo.html">+    <Param name="query" value="{searchTerms}"/>+  </Url>+<moz:SearchForm>http://www.holumbus.org/hayoo/hayoo.html</moz:SearchForm>+</OpenSearchDescription>
+ index/hayoo/plus.png view

binary file changed (absent → 185 bytes)

+ index/hayoo/prototype.js view
@@ -0,0 +1,4221 @@+/*  Prototype JavaScript framework, version 1.6.0.2+ *  (c) 2005-2008 Sam Stephenson+ *+ *  Prototype is freely distributable under the terms of an MIT-style license.+ *  For details, see the Prototype web site: http://www.prototypejs.org/+ *+ *--------------------------------------------------------------------------*/++var Prototype = {+  Version: '1.6.0.2',++  Browser: {+    IE:     !!(window.attachEvent && !window.opera),+    Opera:  !!window.opera,+    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,+    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,+    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)+  },++  BrowserFeatures: {+    XPath: !!document.evaluate,+    ElementExtensions: !!window.HTMLElement,+    SpecificElementExtensions:+      document.createElement('div').__proto__ &&+      document.createElement('div').__proto__ !==+        document.createElement('form').__proto__+  },++  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',+  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,++  emptyFunction: function() { },+  K: function(x) { return x }+};++if (Prototype.Browser.MobileSafari)+  Prototype.BrowserFeatures.SpecificElementExtensions = false;+++/* Based on Alex Arnell's inheritance implementation. */+var Class = {+  create: function() {+    var parent = null, properties = $A(arguments);+    if (Object.isFunction(properties[0]))+      parent = properties.shift();++    function klass() {+      this.initialize.apply(this, arguments);+    }++    Object.extend(klass, Class.Methods);+    klass.superclass = parent;+    klass.subclasses = [];++    if (parent) {+      var subclass = function() { };+      subclass.prototype = parent.prototype;+      klass.prototype = new subclass;+      parent.subclasses.push(klass);+    }++    for (var i = 0; i < properties.length; i++)+      klass.addMethods(properties[i]);++    if (!klass.prototype.initialize)+      klass.prototype.initialize = Prototype.emptyFunction;++    klass.prototype.constructor = klass;++    return klass;+  }+};++Class.Methods = {+  addMethods: function(source) {+    var ancestor   = this.superclass && this.superclass.prototype;+    var properties = Object.keys(source);++    if (!Object.keys({ toString: true }).length)+      properties.push("toString", "valueOf");++    for (var i = 0, length = properties.length; i < length; i++) {+      var property = properties[i], value = source[property];+      if (ancestor && Object.isFunction(value) &&+          value.argumentNames().first() == "$super") {+        var method = value, value = Object.extend((function(m) {+          return function() { return ancestor[m].apply(this, arguments) };+        })(property).wrap(method), {+          valueOf:  function() { return method },+          toString: function() { return method.toString() }+        });+      }+      this.prototype[property] = value;+    }++    return this;+  }+};++var Abstract = { };++Object.extend = function(destination, source) {+  for (var property in source)+    destination[property] = source[property];+  return destination;+};++Object.extend(Object, {+  inspect: function(object) {+    try {+      if (Object.isUndefined(object)) return 'undefined';+      if (object === null) return 'null';+      return object.inspect ? object.inspect() : String(object);+    } catch (e) {+      if (e instanceof RangeError) return '...';+      throw e;+    }+  },++  toJSON: function(object) {+    var type = typeof object;+    switch (type) {+      case 'undefined':+      case 'function':+      case 'unknown': return;+      case 'boolean': return object.toString();+    }++    if (object === null) return 'null';+    if (object.toJSON) return object.toJSON();+    if (Object.isElement(object)) return;++    var results = [];+    for (var property in object) {+      var value = Object.toJSON(object[property]);+      if (!Object.isUndefined(value))+        results.push(property.toJSON() + ': ' + value);+    }++    return '{' + results.join(', ') + '}';+  },++  toQueryString: function(object) {+    return $H(object).toQueryString();+  },++  toHTML: function(object) {+    return object && object.toHTML ? object.toHTML() : String.interpret(object);+  },++  keys: function(object) {+    var keys = [];+    for (var property in object)+      keys.push(property);+    return keys;+  },++  values: function(object) {+    var values = [];+    for (var property in object)+      values.push(object[property]);+    return values;+  },++  clone: function(object) {+    return Object.extend({ }, object);+  },++  isElement: function(object) {+    return object && object.nodeType == 1;+  },++  isArray: function(object) {+    return object != null && typeof object == "object" &&+      'splice' in object && 'join' in object;+  },++  isHash: function(object) {+    return object instanceof Hash;+  },++  isFunction: function(object) {+    return typeof object == "function";+  },++  isString: function(object) {+    return typeof object == "string";+  },++  isNumber: function(object) {+    return typeof object == "number";+  },++  isUndefined: function(object) {+    return typeof object == "undefined";+  }+});++Object.extend(Function.prototype, {+  argumentNames: function() {+    var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip");+    return names.length == 1 && !names[0] ? [] : names;+  },++  bind: function() {+    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;+    var __method = this, args = $A(arguments), object = args.shift();+    return function() {+      return __method.apply(object, args.concat($A(arguments)));+    }+  },++  bindAsEventListener: function() {+    var __method = this, args = $A(arguments), object = args.shift();+    return function(event) {+      return __method.apply(object, [event || window.event].concat(args));+    }+  },++  curry: function() {+    if (!arguments.length) return this;+    var __method = this, args = $A(arguments);+    return function() {+      return __method.apply(this, args.concat($A(arguments)));+    }+  },++  delay: function() {+    var __method = this, args = $A(arguments), timeout = args.shift() * 1000;+    return window.setTimeout(function() {+      return __method.apply(__method, args);+    }, timeout);+  },++  wrap: function(wrapper) {+    var __method = this;+    return function() {+      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));+    }+  },++  methodize: function() {+    if (this._methodized) return this._methodized;+    var __method = this;+    return this._methodized = function() {+      return __method.apply(null, [this].concat($A(arguments)));+    };+  }+});++Function.prototype.defer = Function.prototype.delay.curry(0.01);++Date.prototype.toJSON = function() {+  return '"' + this.getUTCFullYear() + '-' ++    (this.getUTCMonth() + 1).toPaddedString(2) + '-' ++    this.getUTCDate().toPaddedString(2) + 'T' ++    this.getUTCHours().toPaddedString(2) + ':' ++    this.getUTCMinutes().toPaddedString(2) + ':' ++    this.getUTCSeconds().toPaddedString(2) + 'Z"';+};++var Try = {+  these: function() {+    var returnValue;++    for (var i = 0, length = arguments.length; i < length; i++) {+      var lambda = arguments[i];+      try {+        returnValue = lambda();+        break;+      } catch (e) { }+    }++    return returnValue;+  }+};++RegExp.prototype.match = RegExp.prototype.test;++RegExp.escape = function(str) {+  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');+};++/*--------------------------------------------------------------------------*/++var PeriodicalExecuter = Class.create({+  initialize: function(callback, frequency) {+    this.callback = callback;+    this.frequency = frequency;+    this.currentlyExecuting = false;++    this.registerCallback();+  },++  registerCallback: function() {+    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);+  },++  execute: function() {+    this.callback(this);+  },++  stop: function() {+    if (!this.timer) return;+    clearInterval(this.timer);+    this.timer = null;+  },++  onTimerEvent: function() {+    if (!this.currentlyExecuting) {+      try {+        this.currentlyExecuting = true;+        this.execute();+      } finally {+        this.currentlyExecuting = false;+      }+    }+  }+});+Object.extend(String, {+  interpret: function(value) {+    return value == null ? '' : String(value);+  },+  specialChar: {+    '\b': '\\b',+    '\t': '\\t',+    '\n': '\\n',+    '\f': '\\f',+    '\r': '\\r',+    '\\': '\\\\'+  }+});++Object.extend(String.prototype, {+  gsub: function(pattern, replacement) {+    var result = '', source = this, match;+    replacement = arguments.callee.prepareReplacement(replacement);++    while (source.length > 0) {+      if (match = source.match(pattern)) {+        result += source.slice(0, match.index);+        result += String.interpret(replacement(match));+        source  = source.slice(match.index + match[0].length);+      } else {+        result += source, source = '';+      }+    }+    return result;+  },++  sub: function(pattern, replacement, count) {+    replacement = this.gsub.prepareReplacement(replacement);+    count = Object.isUndefined(count) ? 1 : count;++    return this.gsub(pattern, function(match) {+      if (--count < 0) return match[0];+      return replacement(match);+    });+  },++  scan: function(pattern, iterator) {+    this.gsub(pattern, iterator);+    return String(this);+  },++  truncate: function(length, truncation) {+    length = length || 30;+    truncation = Object.isUndefined(truncation) ? '...' : truncation;+    return this.length > length ?+      this.slice(0, length - truncation.length) + truncation : String(this);+  },++  strip: function() {+    return this.replace(/^\s+/, '').replace(/\s+$/, '');+  },++  stripTags: function() {+    return this.replace(/<\/?[^>]+>/gi, '');+  },++  stripScripts: function() {+    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');+  },++  extractScripts: function() {+    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');+    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');+    return (this.match(matchAll) || []).map(function(scriptTag) {+      return (scriptTag.match(matchOne) || ['', ''])[1];+    });+  },++  evalScripts: function() {+    return this.extractScripts().map(function(script) { return eval(script) });+  },++  escapeHTML: function() {+    var self = arguments.callee;+    self.text.data = this;+    return self.div.innerHTML;+  },++  unescapeHTML: function() {+    var div = new Element('div');+    div.innerHTML = this.stripTags();+    return div.childNodes[0] ? (div.childNodes.length > 1 ?+      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :+      div.childNodes[0].nodeValue) : '';+  },++  toQueryParams: function(separator) {+    var match = this.strip().match(/([^?#]*)(#.*)?$/);+    if (!match) return { };++    return match[1].split(separator || '&').inject({ }, function(hash, pair) {+      if ((pair = pair.split('='))[0]) {+        var key = decodeURIComponent(pair.shift());+        var value = pair.length > 1 ? pair.join('=') : pair[0];+        if (value != undefined) value = decodeURIComponent(value);++        if (key in hash) {+          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];+          hash[key].push(value);+        }+        else hash[key] = value;+      }+      return hash;+    });+  },++  toArray: function() {+    return this.split('');+  },++  succ: function() {+    return this.slice(0, this.length - 1) ++      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);+  },++  times: function(count) {+    return count < 1 ? '' : new Array(count + 1).join(this);+  },++  camelize: function() {+    var parts = this.split('-'), len = parts.length;+    if (len == 1) return parts[0];++    var camelized = this.charAt(0) == '-'+      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)+      : parts[0];++    for (var i = 1; i < len; i++)+      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);++    return camelized;+  },++  capitalize: function() {+    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();+  },++  underscore: function() {+    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();+  },++  dasherize: function() {+    return this.gsub(/_/,'-');+  },++  inspect: function(useDoubleQuotes) {+    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {+      var character = String.specialChar[match[0]];+      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);+    });+    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';+    return "'" + escapedString.replace(/'/g, '\\\'') + "'";+  },++  toJSON: function() {+    return this.inspect(true);+  },++  unfilterJSON: function(filter) {+    return this.sub(filter || Prototype.JSONFilter, '#{1}');+  },++  isJSON: function() {+    var str = this;+    if (str.blank()) return false;+    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');+    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);+  },++  evalJSON: function(sanitize) {+    var json = this.unfilterJSON();+    try {+      if (!sanitize || json.isJSON()) return eval('(' + json + ')');+    } catch (e) { }+    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());+  },++  include: function(pattern) {+    return this.indexOf(pattern) > -1;+  },++  startsWith: function(pattern) {+    return this.indexOf(pattern) === 0;+  },++  endsWith: function(pattern) {+    var d = this.length - pattern.length;+    return d >= 0 && this.lastIndexOf(pattern) === d;+  },++  empty: function() {+    return this == '';+  },++  blank: function() {+    return /^\s*$/.test(this);+  },++  interpolate: function(object, pattern) {+    return new Template(this, pattern).evaluate(object);+  }+});++if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {+  escapeHTML: function() {+    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');+  },+  unescapeHTML: function() {+    return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');+  }+});++String.prototype.gsub.prepareReplacement = function(replacement) {+  if (Object.isFunction(replacement)) return replacement;+  var template = new Template(replacement);+  return function(match) { return template.evaluate(match) };+};++String.prototype.parseQuery = String.prototype.toQueryParams;++Object.extend(String.prototype.escapeHTML, {+  div:  document.createElement('div'),+  text: document.createTextNode('')+});++with (String.prototype.escapeHTML) div.appendChild(text);++var Template = Class.create({+  initialize: function(template, pattern) {+    this.template = template.toString();+    this.pattern = pattern || Template.Pattern;+  },++  evaluate: function(object) {+    if (Object.isFunction(object.toTemplateReplacements))+      object = object.toTemplateReplacements();++    return this.template.gsub(this.pattern, function(match) {+      if (object == null) return '';++      var before = match[1] || '';+      if (before == '\\') return match[2];++      var ctx = object, expr = match[3];+      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;+      match = pattern.exec(expr);+      if (match == null) return before;++      while (match != null) {+        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];+        ctx = ctx[comp];+        if (null == ctx || '' == match[3]) break;+        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);+        match = pattern.exec(expr);+      }++      return before + String.interpret(ctx);+    });+  }+});+Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;++var $break = { };++var Enumerable = {+  each: function(iterator, context) {+    var index = 0;+    iterator = iterator.bind(context);+    try {+      this._each(function(value) {+        iterator(value, index++);+      });+    } catch (e) {+      if (e != $break) throw e;+    }+    return this;+  },++  eachSlice: function(number, iterator, context) {+    iterator = iterator ? iterator.bind(context) : Prototype.K;+    var index = -number, slices = [], array = this.toArray();+    while ((index += number) < array.length)+      slices.push(array.slice(index, index+number));+    return slices.collect(iterator, context);+  },++  all: function(iterator, context) {+    iterator = iterator ? iterator.bind(context) : Prototype.K;+    var result = true;+    this.each(function(value, index) {+      result = result && !!iterator(value, index);+      if (!result) throw $break;+    });+    return result;+  },++  any: function(iterator, context) {+    iterator = iterator ? iterator.bind(context) : Prototype.K;+    var result = false;+    this.each(function(value, index) {+      if (result = !!iterator(value, index))+        throw $break;+    });+    return result;+  },++  collect: function(iterator, context) {+    iterator = iterator ? iterator.bind(context) : Prototype.K;+    var results = [];+    this.each(function(value, index) {+      results.push(iterator(value, index));+    });+    return results;+  },++  detect: function(iterator, context) {+    iterator = iterator.bind(context);+    var result;+    this.each(function(value, index) {+      if (iterator(value, index)) {+        result = value;+        throw $break;+      }+    });+    return result;+  },++  findAll: function(iterator, context) {+    iterator = iterator.bind(context);+    var results = [];+    this.each(function(value, index) {+      if (iterator(value, index))+        results.push(value);+    });+    return results;+  },++  grep: function(filter, iterator, context) {+    iterator = iterator ? iterator.bind(context) : Prototype.K;+    var results = [];++    if (Object.isString(filter))+      filter = new RegExp(filter);++    this.each(function(value, index) {+      if (filter.match(value))+        results.push(iterator(value, index));+    });+    return results;+  },++  include: function(object) {+    if (Object.isFunction(this.indexOf))+      if (this.indexOf(object) != -1) return true;++    var found = false;+    this.each(function(value) {+      if (value == object) {+        found = true;+        throw $break;+      }+    });+    return found;+  },++  inGroupsOf: function(number, fillWith) {+    fillWith = Object.isUndefined(fillWith) ? null : fillWith;+    return this.eachSlice(number, function(slice) {+      while(slice.length < number) slice.push(fillWith);+      return slice;+    });+  },++  inject: function(memo, iterator, context) {+    iterator = iterator.bind(context);+    this.each(function(value, index) {+      memo = iterator(memo, value, index);+    });+    return memo;+  },++  invoke: function(method) {+    var args = $A(arguments).slice(1);+    return this.map(function(value) {+      return value[method].apply(value, args);+    });+  },++  max: function(iterator, context) {+    iterator = iterator ? iterator.bind(context) : Prototype.K;+    var result;+    this.each(function(value, index) {+      value = iterator(value, index);+      if (result == null || value >= result)+        result = value;+    });+    return result;+  },++  min: function(iterator, context) {+    iterator = iterator ? iterator.bind(context) : Prototype.K;+    var result;+    this.each(function(value, index) {+      value = iterator(value, index);+      if (result == null || value < result)+        result = value;+    });+    return result;+  },++  partition: function(iterator, context) {+    iterator = iterator ? iterator.bind(context) : Prototype.K;+    var trues = [], falses = [];+    this.each(function(value, index) {+      (iterator(value, index) ?+        trues : falses).push(value);+    });+    return [trues, falses];+  },++  pluck: function(property) {+    var results = [];+    this.each(function(value) {+      results.push(value[property]);+    });+    return results;+  },++  reject: function(iterator, context) {+    iterator = iterator.bind(context);+    var results = [];+    this.each(function(value, index) {+      if (!iterator(value, index))+        results.push(value);+    });+    return results;+  },++  sortBy: function(iterator, context) {+    iterator = iterator.bind(context);+    return this.map(function(value, index) {+      return {value: value, criteria: iterator(value, index)};+    }).sort(function(left, right) {+      var a = left.criteria, b = right.criteria;+      return a < b ? -1 : a > b ? 1 : 0;+    }).pluck('value');+  },++  toArray: function() {+    return this.map();+  },++  zip: function() {+    var iterator = Prototype.K, args = $A(arguments);+    if (Object.isFunction(args.last()))+      iterator = args.pop();++    var collections = [this].concat(args).map($A);+    return this.map(function(value, index) {+      return iterator(collections.pluck(index));+    });+  },++  size: function() {+    return this.toArray().length;+  },++  inspect: function() {+    return '#<Enumerable:' + this.toArray().inspect() + '>';+  }+};++Object.extend(Enumerable, {+  map:     Enumerable.collect,+  find:    Enumerable.detect,+  select:  Enumerable.findAll,+  filter:  Enumerable.findAll,+  member:  Enumerable.include,+  entries: Enumerable.toArray,+  every:   Enumerable.all,+  some:    Enumerable.any+});+function $A(iterable) {+  if (!iterable) return [];+  if (iterable.toArray) return iterable.toArray();+  var length = iterable.length || 0, results = new Array(length);+  while (length--) results[length] = iterable[length];+  return results;+}++if (Prototype.Browser.WebKit) {+  $A = function(iterable) {+    if (!iterable) return [];+    if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&+        iterable.toArray) return iterable.toArray();+    var length = iterable.length || 0, results = new Array(length);+    while (length--) results[length] = iterable[length];+    return results;+  };+}++Array.from = $A;++Object.extend(Array.prototype, Enumerable);++if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;++Object.extend(Array.prototype, {+  _each: function(iterator) {+    for (var i = 0, length = this.length; i < length; i++)+      iterator(this[i]);+  },++  clear: function() {+    this.length = 0;+    return this;+  },++  first: function() {+    return this[0];+  },++  last: function() {+    return this[this.length - 1];+  },++  compact: function() {+    return this.select(function(value) {+      return value != null;+    });+  },++  flatten: function() {+    return this.inject([], function(array, value) {+      return array.concat(Object.isArray(value) ?+        value.flatten() : [value]);+    });+  },++  without: function() {+    var values = $A(arguments);+    return this.select(function(value) {+      return !values.include(value);+    });+  },++  reverse: function(inline) {+    return (inline !== false ? this : this.toArray())._reverse();+  },++  reduce: function() {+    return this.length > 1 ? this : this[0];+  },++  uniq: function(sorted) {+    return this.inject([], function(array, value, index) {+      if (0 == index || (sorted ? array.last() != value : !array.include(value)))+        array.push(value);+      return array;+    });+  },++  intersect: function(array) {+    return this.uniq().findAll(function(item) {+      return array.detect(function(value) { return item === value });+    });+  },++  clone: function() {+    return [].concat(this);+  },++  size: function() {+    return this.length;+  },++  inspect: function() {+    return '[' + this.map(Object.inspect).join(', ') + ']';+  },++  toJSON: function() {+    var results = [];+    this.each(function(object) {+      var value = Object.toJSON(object);+      if (!Object.isUndefined(value)) results.push(value);+    });+    return '[' + results.join(', ') + ']';+  }+});++// use native browser JS 1.6 implementation if available+if (Object.isFunction(Array.prototype.forEach))+  Array.prototype._each = Array.prototype.forEach;++if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {+  i || (i = 0);+  var length = this.length;+  if (i < 0) i = length + i;+  for (; i < length; i++)+    if (this[i] === item) return i;+  return -1;+};++if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {+  i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;+  var n = this.slice(0, i).reverse().indexOf(item);+  return (n < 0) ? n : i - n - 1;+};++Array.prototype.toArray = Array.prototype.clone;++function $w(string) {+  if (!Object.isString(string)) return [];+  string = string.strip();+  return string ? string.split(/\s+/) : [];+}++if (Prototype.Browser.Opera){+  Array.prototype.concat = function() {+    var array = [];+    for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);+    for (var i = 0, length = arguments.length; i < length; i++) {+      if (Object.isArray(arguments[i])) {+        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)+          array.push(arguments[i][j]);+      } else {+        array.push(arguments[i]);+      }+    }+    return array;+  };+}+Object.extend(Number.prototype, {+  toColorPart: function() {+    return this.toPaddedString(2, 16);+  },++  succ: function() {+    return this + 1;+  },++  times: function(iterator) {+    $R(0, this, true).each(iterator);+    return this;+  },++  toPaddedString: function(length, radix) {+    var string = this.toString(radix || 10);+    return '0'.times(length - string.length) + string;+  },++  toJSON: function() {+    return isFinite(this) ? this.toString() : 'null';+  }+});++$w('abs round ceil floor').each(function(method){+  Number.prototype[method] = Math[method].methodize();+});+function $H(object) {+  return new Hash(object);+};++var Hash = Class.create(Enumerable, (function() {++  function toQueryPair(key, value) {+    if (Object.isUndefined(value)) return key;+    return key + '=' + encodeURIComponent(String.interpret(value));+  }++  return {+    initialize: function(object) {+      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);+    },++    _each: function(iterator) {+      for (var key in this._object) {+        var value = this._object[key], pair = [key, value];+        pair.key = key;+        pair.value = value;+        iterator(pair);+      }+    },++    set: function(key, value) {+      return this._object[key] = value;+    },++    get: function(key) {+      return this._object[key];+    },++    unset: function(key) {+      var value = this._object[key];+      delete this._object[key];+      return value;+    },++    toObject: function() {+      return Object.clone(this._object);+    },++    keys: function() {+      return this.pluck('key');+    },++    values: function() {+      return this.pluck('value');+    },++    index: function(value) {+      var match = this.detect(function(pair) {+        return pair.value === value;+      });+      return match && match.key;+    },++    merge: function(object) {+      return this.clone().update(object);+    },++    update: function(object) {+      return new Hash(object).inject(this, function(result, pair) {+        result.set(pair.key, pair.value);+        return result;+      });+    },++    toQueryString: function() {+      return this.map(function(pair) {+        var key = encodeURIComponent(pair.key), values = pair.value;++        if (values && typeof values == 'object') {+          if (Object.isArray(values))+            return values.map(toQueryPair.curry(key)).join('&');+        }+        return toQueryPair(key, values);+      }).join('&');+    },++    inspect: function() {+      return '#<Hash:{' + this.map(function(pair) {+        return pair.map(Object.inspect).join(': ');+      }).join(', ') + '}>';+    },++    toJSON: function() {+      return Object.toJSON(this.toObject());+    },++    clone: function() {+      return new Hash(this);+    }+  }+})());++Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;+Hash.from = $H;+var ObjectRange = Class.create(Enumerable, {+  initialize: function(start, end, exclusive) {+    this.start = start;+    this.end = end;+    this.exclusive = exclusive;+  },++  _each: function(iterator) {+    var value = this.start;+    while (this.include(value)) {+      iterator(value);+      value = value.succ();+    }+  },++  include: function(value) {+    if (value < this.start)+      return false;+    if (this.exclusive)+      return value < this.end;+    return value <= this.end;+  }+});++var $R = function(start, end, exclusive) {+  return new ObjectRange(start, end, exclusive);+};++var Ajax = {+  getTransport: function() {+    return Try.these(+      function() {return new XMLHttpRequest()},+      function() {return new ActiveXObject('Msxml2.XMLHTTP')},+      function() {return new ActiveXObject('Microsoft.XMLHTTP')}+    ) || false;+  },++  activeRequestCount: 0+};++Ajax.Responders = {+  responders: [],++  _each: function(iterator) {+    this.responders._each(iterator);+  },++  register: function(responder) {+    if (!this.include(responder))+      this.responders.push(responder);+  },++  unregister: function(responder) {+    this.responders = this.responders.without(responder);+  },++  dispatch: function(callback, request, transport, json) {+    this.each(function(responder) {+      if (Object.isFunction(responder[callback])) {+        try {+          responder[callback].apply(responder, [request, transport, json]);+        } catch (e) { }+      }+    });+  }+};++Object.extend(Ajax.Responders, Enumerable);++Ajax.Responders.register({+  onCreate:   function() { Ajax.activeRequestCount++ },+  onComplete: function() { Ajax.activeRequestCount-- }+});++Ajax.Base = Class.create({+  initialize: function(options) {+    this.options = {+      method:       'post',+      asynchronous: true,+      contentType:  'application/x-www-form-urlencoded',+      encoding:     'UTF-8',+      parameters:   '',+      evalJSON:     true,+      evalJS:       true+    };+    Object.extend(this.options, options || { });++    this.options.method = this.options.method.toLowerCase();++    if (Object.isString(this.options.parameters))+      this.options.parameters = this.options.parameters.toQueryParams();+    else if (Object.isHash(this.options.parameters))+      this.options.parameters = this.options.parameters.toObject();+  }+});++Ajax.Request = Class.create(Ajax.Base, {+  _complete: false,++  initialize: function($super, url, options) {+    $super(options);+    this.transport = Ajax.getTransport();+    this.request(url);+  },++  request: function(url) {+    this.url = url;+    this.method = this.options.method;+    var params = Object.clone(this.options.parameters);++    if (!['get', 'post'].include(this.method)) {+      // simulate other verbs over post+      params['_method'] = this.method;+      this.method = 'post';+    }++    this.parameters = params;++    if (params = Object.toQueryString(params)) {+      // when GET, append parameters to URL+      if (this.method == 'get')+        this.url += (this.url.include('?') ? '&' : '?') + params;+      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))+        params += '&_=';+    }++    try {+      var response = new Ajax.Response(this);+      if (this.options.onCreate) this.options.onCreate(response);+      Ajax.Responders.dispatch('onCreate', this, response);++      this.transport.open(this.method.toUpperCase(), this.url,+        this.options.asynchronous);++      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);++      this.transport.onreadystatechange = this.onStateChange.bind(this);+      this.setRequestHeaders();++      this.body = this.method == 'post' ? (this.options.postBody || params) : null;+      this.transport.send(this.body);++      /* Force Firefox to handle ready state 4 for synchronous requests */+      if (!this.options.asynchronous && this.transport.overrideMimeType)+        this.onStateChange();++    }+    catch (e) {+      this.dispatchException(e);+    }+  },++  onStateChange: function() {+    var readyState = this.transport.readyState;+    if (readyState > 1 && !((readyState == 4) && this._complete))+      this.respondToReadyState(this.transport.readyState);+  },++  setRequestHeaders: function() {+    var headers = {+      'X-Requested-With': 'XMLHttpRequest',+      'X-Prototype-Version': Prototype.Version,+      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'+    };++    if (this.method == 'post') {+      headers['Content-type'] = this.options.contentType ++        (this.options.encoding ? '; charset=' + this.options.encoding : '');++      /* Force "Connection: close" for older Mozilla browsers to work+       * around a bug where XMLHttpRequest sends an incorrect+       * Content-length header. See Mozilla Bugzilla #246651.+       */+      if (this.transport.overrideMimeType &&+          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)+            headers['Connection'] = 'close';+    }++    // user-defined headers+    if (typeof this.options.requestHeaders == 'object') {+      var extras = this.options.requestHeaders;++      if (Object.isFunction(extras.push))+        for (var i = 0, length = extras.length; i < length; i += 2)+          headers[extras[i]] = extras[i+1];+      else+        $H(extras).each(function(pair) { headers[pair.key] = pair.value });+    }++    for (var name in headers)+      this.transport.setRequestHeader(name, headers[name]);+  },++  success: function() {+    var status = this.getStatus();+    return !status || (status >= 200 && status < 300);+  },++  getStatus: function() {+    try {+      return this.transport.status || 0;+    } catch (e) { return 0 }+  },++  respondToReadyState: function(readyState) {+    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);++    if (state == 'Complete') {+      try {+        this._complete = true;+        (this.options['on' + response.status]+         || this.options['on' + (this.success() ? 'Success' : 'Failure')]+         || Prototype.emptyFunction)(response, response.headerJSON);+      } catch (e) {+        this.dispatchException(e);+      }++      var contentType = response.getHeader('Content-type');+      if (this.options.evalJS == 'force'+          || (this.options.evalJS && this.isSameOrigin() && contentType+          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))+        this.evalResponse();+    }++    try {+      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);+      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);+    } catch (e) {+      this.dispatchException(e);+    }++    if (state == 'Complete') {+      // avoid memory leak in MSIE: clean up+      this.transport.onreadystatechange = Prototype.emptyFunction;+    }+  },++  isSameOrigin: function() {+    var m = this.url.match(/^\s*https?:\/\/[^\/]*/);+    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({+      protocol: location.protocol,+      domain: document.domain,+      port: location.port ? ':' + location.port : ''+    }));+  },++  getHeader: function(name) {+    try {+      return this.transport.getResponseHeader(name) || null;+    } catch (e) { return null }+  },++  evalResponse: function() {+    try {+      return eval((this.transport.responseText || '').unfilterJSON());+    } catch (e) {+      this.dispatchException(e);+    }+  },++  dispatchException: function(exception) {+    (this.options.onException || Prototype.emptyFunction)(this, exception);+    Ajax.Responders.dispatch('onException', this, exception);+  }+});++Ajax.Request.Events =+  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];++Ajax.Response = Class.create({+  initialize: function(request){+    this.request = request;+    var transport  = this.transport  = request.transport,+        readyState = this.readyState = transport.readyState;++    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {+      this.status       = this.getStatus();+      this.statusText   = this.getStatusText();+      this.responseText = String.interpret(transport.responseText);+      this.headerJSON   = this._getHeaderJSON();+    }++    if(readyState == 4) {+      var xml = transport.responseXML;+      this.responseXML  = Object.isUndefined(xml) ? null : xml;+      this.responseJSON = this._getResponseJSON();+    }+  },++  status:      0,+  statusText: '',++  getStatus: Ajax.Request.prototype.getStatus,++  getStatusText: function() {+    try {+      return this.transport.statusText || '';+    } catch (e) { return '' }+  },++  getHeader: Ajax.Request.prototype.getHeader,++  getAllHeaders: function() {+    try {+      return this.getAllResponseHeaders();+    } catch (e) { return null }+  },++  getResponseHeader: function(name) {+    return this.transport.getResponseHeader(name);+  },++  getAllResponseHeaders: function() {+    return this.transport.getAllResponseHeaders();+  },++  _getHeaderJSON: function() {+    var json = this.getHeader('X-JSON');+    if (!json) return null;+    json = decodeURIComponent(escape(json));+    try {+      return json.evalJSON(this.request.options.sanitizeJSON ||+        !this.request.isSameOrigin());+    } catch (e) {+      this.request.dispatchException(e);+    }+  },++  _getResponseJSON: function() {+    var options = this.request.options;+    if (!options.evalJSON || (options.evalJSON != 'force' &&+      !(this.getHeader('Content-type') || '').include('application/json')) ||+        this.responseText.blank())+          return null;+    try {+      return this.responseText.evalJSON(options.sanitizeJSON ||+        !this.request.isSameOrigin());+    } catch (e) {+      this.request.dispatchException(e);+    }+  }+});++Ajax.Updater = Class.create(Ajax.Request, {+  initialize: function($super, container, url, options) {+    this.container = {+      success: (container.success || container),+      failure: (container.failure || (container.success ? null : container))+    };++    options = Object.clone(options);+    var onComplete = options.onComplete;+    options.onComplete = (function(response, json) {+      this.updateContent(response.responseText);+      if (Object.isFunction(onComplete)) onComplete(response, json);+    }).bind(this);++    $super(url, options);+  },++  updateContent: function(responseText) {+    var receiver = this.container[this.success() ? 'success' : 'failure'],+        options = this.options;++    if (!options.evalScripts) responseText = responseText.stripScripts();++    if (receiver = $(receiver)) {+      if (options.insertion) {+        if (Object.isString(options.insertion)) {+          var insertion = { }; insertion[options.insertion] = responseText;+          receiver.insert(insertion);+        }+        else options.insertion(receiver, responseText);+      }+      else receiver.update(responseText);+    }+  }+});++Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {+  initialize: function($super, container, url, options) {+    $super(options);+    this.onComplete = this.options.onComplete;++    this.frequency = (this.options.frequency || 2);+    this.decay = (this.options.decay || 1);++    this.updater = { };+    this.container = container;+    this.url = url;++    this.start();+  },++  start: function() {+    this.options.onComplete = this.updateComplete.bind(this);+    this.onTimerEvent();+  },++  stop: function() {+    this.updater.options.onComplete = undefined;+    clearTimeout(this.timer);+    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);+  },++  updateComplete: function(response) {+    if (this.options.decay) {+      this.decay = (response.responseText == this.lastText ?+        this.decay * this.options.decay : 1);++      this.lastText = response.responseText;+    }+    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);+  },++  onTimerEvent: function() {+    this.updater = new Ajax.Updater(this.container, this.url, this.options);+  }+});+function $(element) {+  if (arguments.length > 1) {+    for (var i = 0, elements = [], length = arguments.length; i < length; i++)+      elements.push($(arguments[i]));+    return elements;+  }+  if (Object.isString(element))+    element = document.getElementById(element);+  return Element.extend(element);+}++if (Prototype.BrowserFeatures.XPath) {+  document._getElementsByXPath = function(expression, parentElement) {+    var results = [];+    var query = document.evaluate(expression, $(parentElement) || document,+      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);+    for (var i = 0, length = query.snapshotLength; i < length; i++)+      results.push(Element.extend(query.snapshotItem(i)));+    return results;+  };+}++/*--------------------------------------------------------------------------*/++if (!window.Node) var Node = { };++if (!Node.ELEMENT_NODE) {+  // DOM level 2 ECMAScript Language Binding+  Object.extend(Node, {+    ELEMENT_NODE: 1,+    ATTRIBUTE_NODE: 2,+    TEXT_NODE: 3,+    CDATA_SECTION_NODE: 4,+    ENTITY_REFERENCE_NODE: 5,+    ENTITY_NODE: 6,+    PROCESSING_INSTRUCTION_NODE: 7,+    COMMENT_NODE: 8,+    DOCUMENT_NODE: 9,+    DOCUMENT_TYPE_NODE: 10,+    DOCUMENT_FRAGMENT_NODE: 11,+    NOTATION_NODE: 12+  });+}++(function() {+  var element = this.Element;+  this.Element = function(tagName, attributes) {+    attributes = attributes || { };+    tagName = tagName.toLowerCase();+    var cache = Element.cache;+    if (Prototype.Browser.IE && attributes.name) {+      tagName = '<' + tagName + ' name="' + attributes.name + '">';+      delete attributes.name;+      return Element.writeAttribute(document.createElement(tagName), attributes);+    }+    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));+    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);+  };+  Object.extend(this.Element, element || { });+}).call(window);++Element.cache = { };++Element.Methods = {+  visible: function(element) {+    return $(element).style.display != 'none';+  },++  toggle: function(element) {+    element = $(element);+    Element[Element.visible(element) ? 'hide' : 'show'](element);+    return element;+  },++  hide: function(element) {+    $(element).style.display = 'none';+    return element;+  },++  show: function(element) {+    $(element).style.display = '';+    return element;+  },++  remove: function(element) {+    element = $(element);+    element.parentNode.removeChild(element);+    return element;+  },++  update: function(element, content) {+    element = $(element);+    if (content && content.toElement) content = content.toElement();+    if (Object.isElement(content)) return element.update().insert(content);+    content = Object.toHTML(content);+    element.innerHTML = content.stripScripts();+    content.evalScripts.bind(content).defer();+    return element;+  },++  replace: function(element, content) {+    element = $(element);+    if (content && content.toElement) content = content.toElement();+    else if (!Object.isElement(content)) {+      content = Object.toHTML(content);+      var range = element.ownerDocument.createRange();+      range.selectNode(element);+      content.evalScripts.bind(content).defer();+      content = range.createContextualFragment(content.stripScripts());+    }+    element.parentNode.replaceChild(content, element);+    return element;+  },++  insert: function(element, insertions) {+    element = $(element);++    if (Object.isString(insertions) || Object.isNumber(insertions) ||+        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))+          insertions = {bottom:insertions};++    var content, insert, tagName, childNodes;++    for (var position in insertions) {+      content  = insertions[position];+      position = position.toLowerCase();+      insert = Element._insertionTranslations[position];++      if (content && content.toElement) content = content.toElement();+      if (Object.isElement(content)) {+        insert(element, content);+        continue;+      }++      content = Object.toHTML(content);++      tagName = ((position == 'before' || position == 'after')+        ? element.parentNode : element).tagName.toUpperCase();++      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());++      if (position == 'top' || position == 'after') childNodes.reverse();+      childNodes.each(insert.curry(element));++      content.evalScripts.bind(content).defer();+    }++    return element;+  },++  wrap: function(element, wrapper, attributes) {+    element = $(element);+    if (Object.isElement(wrapper))+      $(wrapper).writeAttribute(attributes || { });+    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);+    else wrapper = new Element('div', wrapper);+    if (element.parentNode)+      element.parentNode.replaceChild(wrapper, element);+    wrapper.appendChild(element);+    return wrapper;+  },++  inspect: function(element) {+    element = $(element);+    var result = '<' + element.tagName.toLowerCase();+    $H({'id': 'id', 'className': 'class'}).each(function(pair) {+      var property = pair.first(), attribute = pair.last();+      var value = (element[property] || '').toString();+      if (value) result += ' ' + attribute + '=' + value.inspect(true);+    });+    return result + '>';+  },++  recursivelyCollect: function(element, property) {+    element = $(element);+    var elements = [];+    while (element = element[property])+      if (element.nodeType == 1)+        elements.push(Element.extend(element));+    return elements;+  },++  ancestors: function(element) {+    return $(element).recursivelyCollect('parentNode');+  },++  descendants: function(element) {+    return $(element).select("*");+  },++  firstDescendant: function(element) {+    element = $(element).firstChild;+    while (element && element.nodeType != 1) element = element.nextSibling;+    return $(element);+  },++  immediateDescendants: function(element) {+    if (!(element = $(element).firstChild)) return [];+    while (element && element.nodeType != 1) element = element.nextSibling;+    if (element) return [element].concat($(element).nextSiblings());+    return [];+  },++  previousSiblings: function(element) {+    return $(element).recursivelyCollect('previousSibling');+  },++  nextSiblings: function(element) {+    return $(element).recursivelyCollect('nextSibling');+  },++  siblings: function(element) {+    element = $(element);+    return element.previousSiblings().reverse().concat(element.nextSiblings());+  },++  match: function(element, selector) {+    if (Object.isString(selector))+      selector = new Selector(selector);+    return selector.match($(element));+  },++  up: function(element, expression, index) {+    element = $(element);+    if (arguments.length == 1) return $(element.parentNode);+    var ancestors = element.ancestors();+    return Object.isNumber(expression) ? ancestors[expression] :+      Selector.findElement(ancestors, expression, index);+  },++  down: function(element, expression, index) {+    element = $(element);+    if (arguments.length == 1) return element.firstDescendant();+    return Object.isNumber(expression) ? element.descendants()[expression] :+      element.select(expression)[index || 0];+  },++  previous: function(element, expression, index) {+    element = $(element);+    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));+    var previousSiblings = element.previousSiblings();+    return Object.isNumber(expression) ? previousSiblings[expression] :+      Selector.findElement(previousSiblings, expression, index);+  },++  next: function(element, expression, index) {+    element = $(element);+    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));+    var nextSiblings = element.nextSiblings();+    return Object.isNumber(expression) ? nextSiblings[expression] :+      Selector.findElement(nextSiblings, expression, index);+  },++  select: function() {+    var args = $A(arguments), element = $(args.shift());+    return Selector.findChildElements(element, args);+  },++  adjacent: function() {+    var args = $A(arguments), element = $(args.shift());+    return Selector.findChildElements(element.parentNode, args).without(element);+  },++  identify: function(element) {+    element = $(element);+    var id = element.readAttribute('id'), self = arguments.callee;+    if (id) return id;+    do { id = 'anonymous_element_' + self.counter++ } while ($(id));+    element.writeAttribute('id', id);+    return id;+  },++  readAttribute: function(element, name) {+    element = $(element);+    if (Prototype.Browser.IE) {+      var t = Element._attributeTranslations.read;+      if (t.values[name]) return t.values[name](element, name);+      if (t.names[name]) name = t.names[name];+      if (name.include(':')) {+        return (!element.attributes || !element.attributes[name]) ? null :+         element.attributes[name].value;+      }+    }+    return element.getAttribute(name);+  },++  writeAttribute: function(element, name, value) {+    element = $(element);+    var attributes = { }, t = Element._attributeTranslations.write;++    if (typeof name == 'object') attributes = name;+    else attributes[name] = Object.isUndefined(value) ? true : value;++    for (var attr in attributes) {+      name = t.names[attr] || attr;+      value = attributes[attr];+      if (t.values[attr]) name = t.values[attr](element, value);+      if (value === false || value === null)+        element.removeAttribute(name);+      else if (value === true)+        element.setAttribute(name, name);+      else element.setAttribute(name, value);+    }+    return element;+  },++  getHeight: function(element) {+    return $(element).getDimensions().height;+  },++  getWidth: function(element) {+    return $(element).getDimensions().width;+  },++  classNames: function(element) {+    return new Element.ClassNames(element);+  },++  hasClassName: function(element, className) {+    if (!(element = $(element))) return;+    var elementClassName = element.className;+    return (elementClassName.length > 0 && (elementClassName == className ||+      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));+  },++  addClassName: function(element, className) {+    if (!(element = $(element))) return;+    if (!element.hasClassName(className))+      element.className += (element.className ? ' ' : '') + className;+    return element;+  },++  removeClassName: function(element, className) {+    if (!(element = $(element))) return;+    element.className = element.className.replace(+      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();+    return element;+  },++  toggleClassName: function(element, className) {+    if (!(element = $(element))) return;+    return element[element.hasClassName(className) ?+      'removeClassName' : 'addClassName'](className);+  },++  // removes whitespace-only text node children+  cleanWhitespace: function(element) {+    element = $(element);+    var node = element.firstChild;+    while (node) {+      var nextNode = node.nextSibling;+      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))+        element.removeChild(node);+      node = nextNode;+    }+    return element;+  },++  empty: function(element) {+    return $(element).innerHTML.blank();+  },++  descendantOf: function(element, ancestor) {+    element = $(element), ancestor = $(ancestor);+    var originalAncestor = ancestor;++    if (element.compareDocumentPosition)+      return (element.compareDocumentPosition(ancestor) & 8) === 8;++    if (element.sourceIndex && !Prototype.Browser.Opera) {+      var e = element.sourceIndex, a = ancestor.sourceIndex,+       nextAncestor = ancestor.nextSibling;+      if (!nextAncestor) {+        do { ancestor = ancestor.parentNode; }+        while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);+      }+      if (nextAncestor && nextAncestor.sourceIndex)+       return (e > a && e < nextAncestor.sourceIndex);+    }++    while (element = element.parentNode)+      if (element == originalAncestor) return true;+    return false;+  },++  scrollTo: function(element) {+    element = $(element);+    var pos = element.cumulativeOffset();+    window.scrollTo(pos[0], pos[1]);+    return element;+  },++  getStyle: function(element, style) {+    element = $(element);+    style = style == 'float' ? 'cssFloat' : style.camelize();+    var value = element.style[style];+    if (!value) {+      var css = document.defaultView.getComputedStyle(element, null);+      value = css ? css[style] : null;+    }+    if (style == 'opacity') return value ? parseFloat(value) : 1.0;+    return value == 'auto' ? null : value;+  },++  getOpacity: function(element) {+    return $(element).getStyle('opacity');+  },++  setStyle: function(element, styles) {+    element = $(element);+    var elementStyle = element.style, match;+    if (Object.isString(styles)) {+      element.style.cssText += ';' + styles;+      return styles.include('opacity') ?+        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;+    }+    for (var property in styles)+      if (property == 'opacity') element.setOpacity(styles[property]);+      else+        elementStyle[(property == 'float' || property == 'cssFloat') ?+          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :+            property] = styles[property];++    return element;+  },++  setOpacity: function(element, value) {+    element = $(element);+    element.style.opacity = (value == 1 || value === '') ? '' :+      (value < 0.00001) ? 0 : value;+    return element;+  },++  getDimensions: function(element) {+    element = $(element);+    var display = $(element).getStyle('display');+    if (display != 'none' && display != null) // Safari bug+      return {width: element.offsetWidth, height: element.offsetHeight};++    // All *Width and *Height properties give 0 on elements with display none,+    // so enable the element temporarily+    var els = element.style;+    var originalVisibility = els.visibility;+    var originalPosition = els.position;+    var originalDisplay = els.display;+    els.visibility = 'hidden';+    els.position = 'absolute';+    els.display = 'block';+    var originalWidth = element.clientWidth;+    var originalHeight = element.clientHeight;+    els.display = originalDisplay;+    els.position = originalPosition;+    els.visibility = originalVisibility;+    return {width: originalWidth, height: originalHeight};+  },++  makePositioned: function(element) {+    element = $(element);+    var pos = Element.getStyle(element, 'position');+    if (pos == 'static' || !pos) {+      element._madePositioned = true;+      element.style.position = 'relative';+      // Opera returns the offset relative to the positioning context, when an+      // element is position relative but top and left have not been defined+      if (window.opera) {+        element.style.top = 0;+        element.style.left = 0;+      }+    }+    return element;+  },++  undoPositioned: function(element) {+    element = $(element);+    if (element._madePositioned) {+      element._madePositioned = undefined;+      element.style.position =+        element.style.top =+        element.style.left =+        element.style.bottom =+        element.style.right = '';+    }+    return element;+  },++  makeClipping: function(element) {+    element = $(element);+    if (element._overflow) return element;+    element._overflow = Element.getStyle(element, 'overflow') || 'auto';+    if (element._overflow !== 'hidden')+      element.style.overflow = 'hidden';+    return element;+  },++  undoClipping: function(element) {+    element = $(element);+    if (!element._overflow) return element;+    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;+    element._overflow = null;+    return element;+  },++  cumulativeOffset: function(element) {+    var valueT = 0, valueL = 0;+    do {+      valueT += element.offsetTop  || 0;+      valueL += element.offsetLeft || 0;+      element = element.offsetParent;+    } while (element);+    return Element._returnOffset(valueL, valueT);+  },++  positionedOffset: function(element) {+    var valueT = 0, valueL = 0;+    do {+      valueT += element.offsetTop  || 0;+      valueL += element.offsetLeft || 0;+      element = element.offsetParent;+      if (element) {+        if (element.tagName == 'BODY') break;+        var p = Element.getStyle(element, 'position');+        if (p !== 'static') break;+      }+    } while (element);+    return Element._returnOffset(valueL, valueT);+  },++  absolutize: function(element) {+    element = $(element);+    if (element.getStyle('position') == 'absolute') return;+    // Position.prepare(); // To be done manually by Scripty when it needs it.++    var offsets = element.positionedOffset();+    var top     = offsets[1];+    var left    = offsets[0];+    var width   = element.clientWidth;+    var height  = element.clientHeight;++    element._originalLeft   = left - parseFloat(element.style.left  || 0);+    element._originalTop    = top  - parseFloat(element.style.top || 0);+    element._originalWidth  = element.style.width;+    element._originalHeight = element.style.height;++    element.style.position = 'absolute';+    element.style.top    = top + 'px';+    element.style.left   = left + 'px';+    element.style.width  = width + 'px';+    element.style.height = height + 'px';+    return element;+  },++  relativize: function(element) {+    element = $(element);+    if (element.getStyle('position') == 'relative') return;+    // Position.prepare(); // To be done manually by Scripty when it needs it.++    element.style.position = 'relative';+    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);+    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);++    element.style.top    = top + 'px';+    element.style.left   = left + 'px';+    element.style.height = element._originalHeight;+    element.style.width  = element._originalWidth;+    return element;+  },++  cumulativeScrollOffset: function(element) {+    var valueT = 0, valueL = 0;+    do {+      valueT += element.scrollTop  || 0;+      valueL += element.scrollLeft || 0;+      element = element.parentNode;+    } while (element);+    return Element._returnOffset(valueL, valueT);+  },++  getOffsetParent: function(element) {+    if (element.offsetParent) return $(element.offsetParent);+    if (element == document.body) return $(element);++    while ((element = element.parentNode) && element != document.body)+      if (Element.getStyle(element, 'position') != 'static')+        return $(element);++    return $(document.body);+  },++  viewportOffset: function(forElement) {+    var valueT = 0, valueL = 0;++    var element = forElement;+    do {+      valueT += element.offsetTop  || 0;+      valueL += element.offsetLeft || 0;++      // Safari fix+      if (element.offsetParent == document.body &&+        Element.getStyle(element, 'position') == 'absolute') break;++    } while (element = element.offsetParent);++    element = forElement;+    do {+      if (!Prototype.Browser.Opera || element.tagName == 'BODY') {+        valueT -= element.scrollTop  || 0;+        valueL -= element.scrollLeft || 0;+      }+    } while (element = element.parentNode);++    return Element._returnOffset(valueL, valueT);+  },++  clonePosition: function(element, source) {+    var options = Object.extend({+      setLeft:    true,+      setTop:     true,+      setWidth:   true,+      setHeight:  true,+      offsetTop:  0,+      offsetLeft: 0+    }, arguments[2] || { });++    // find page position of source+    source = $(source);+    var p = source.viewportOffset();++    // find coordinate system to use+    element = $(element);+    var delta = [0, 0];+    var parent = null;+    // delta [0,0] will do fine with position: fixed elements,+    // position:absolute needs offsetParent deltas+    if (Element.getStyle(element, 'position') == 'absolute') {+      parent = element.getOffsetParent();+      delta = parent.viewportOffset();+    }++    // correct by body offsets (fixes Safari)+    if (parent == document.body) {+      delta[0] -= document.body.offsetLeft;+      delta[1] -= document.body.offsetTop;+    }++    // set position+    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';+    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';+    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';+    if (options.setHeight) element.style.height = source.offsetHeight + 'px';+    return element;+  }+};++Element.Methods.identify.counter = 1;++Object.extend(Element.Methods, {+  getElementsBySelector: Element.Methods.select,+  childElements: Element.Methods.immediateDescendants+});++Element._attributeTranslations = {+  write: {+    names: {+      className: 'class',+      htmlFor:   'for'+    },+    values: { }+  }+};++if (Prototype.Browser.Opera) {+  Element.Methods.getStyle = Element.Methods.getStyle.wrap(+    function(proceed, element, style) {+      switch (style) {+        case 'left': case 'top': case 'right': case 'bottom':+          if (proceed(element, 'position') === 'static') return null;+        case 'height': case 'width':+          // returns '0px' for hidden elements; we want it to return null+          if (!Element.visible(element)) return null;++          // returns the border-box dimensions rather than the content-box+          // dimensions, so we subtract padding and borders from the value+          var dim = parseInt(proceed(element, style), 10);++          if (dim !== element['offset' + style.capitalize()])+            return dim + 'px';++          var properties;+          if (style === 'height') {+            properties = ['border-top-width', 'padding-top',+             'padding-bottom', 'border-bottom-width'];+          }+          else {+            properties = ['border-left-width', 'padding-left',+             'padding-right', 'border-right-width'];+          }+          return properties.inject(dim, function(memo, property) {+            var val = proceed(element, property);+            return val === null ? memo : memo - parseInt(val, 10);+          }) + 'px';+        default: return proceed(element, style);+      }+    }+  );++  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(+    function(proceed, element, attribute) {+      if (attribute === 'title') return element.title;+      return proceed(element, attribute);+    }+  );+}++else if (Prototype.Browser.IE) {+  // IE doesn't report offsets correctly for static elements, so we change them+  // to "relative" to get the values, then change them back.+  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(+    function(proceed, element) {+      element = $(element);+      var position = element.getStyle('position');+      if (position !== 'static') return proceed(element);+      element.setStyle({ position: 'relative' });+      var value = proceed(element);+      element.setStyle({ position: position });+      return value;+    }+  );++  $w('positionedOffset viewportOffset').each(function(method) {+    Element.Methods[method] = Element.Methods[method].wrap(+      function(proceed, element) {+        element = $(element);+        var position = element.getStyle('position');+        if (position !== 'static') return proceed(element);+        // Trigger hasLayout on the offset parent so that IE6 reports+        // accurate offsetTop and offsetLeft values for position: fixed.+        var offsetParent = element.getOffsetParent();+        if (offsetParent && offsetParent.getStyle('position') === 'fixed')+          offsetParent.setStyle({ zoom: 1 });+        element.setStyle({ position: 'relative' });+        var value = proceed(element);+        element.setStyle({ position: position });+        return value;+      }+    );+  });++  Element.Methods.getStyle = function(element, style) {+    element = $(element);+    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();+    var value = element.style[style];+    if (!value && element.currentStyle) value = element.currentStyle[style];++    if (style == 'opacity') {+      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))+        if (value[1]) return parseFloat(value[1]) / 100;+      return 1.0;+    }++    if (value == 'auto') {+      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))+        return element['offset' + style.capitalize()] + 'px';+      return null;+    }+    return value;+  };++  Element.Methods.setOpacity = function(element, value) {+    function stripAlpha(filter){+      return filter.replace(/alpha\([^\)]*\)/gi,'');+    }+    element = $(element);+    var currentStyle = element.currentStyle;+    if ((currentStyle && !currentStyle.hasLayout) ||+      (!currentStyle && element.style.zoom == 'normal'))+        element.style.zoom = 1;++    var filter = element.getStyle('filter'), style = element.style;+    if (value == 1 || value === '') {+      (filter = stripAlpha(filter)) ?+        style.filter = filter : style.removeAttribute('filter');+      return element;+    } else if (value < 0.00001) value = 0;+    style.filter = stripAlpha(filter) ++      'alpha(opacity=' + (value * 100) + ')';+    return element;+  };++  Element._attributeTranslations = {+    read: {+      names: {+        'class': 'className',+        'for':   'htmlFor'+      },+      values: {+        _getAttr: function(element, attribute) {+          return element.getAttribute(attribute, 2);+        },+        _getAttrNode: function(element, attribute) {+          var node = element.getAttributeNode(attribute);+          return node ? node.value : "";+        },+        _getEv: function(element, attribute) {+          attribute = element.getAttribute(attribute);+          return attribute ? attribute.toString().slice(23, -2) : null;+        },+        _flag: function(element, attribute) {+          return $(element).hasAttribute(attribute) ? attribute : null;+        },+        style: function(element) {+          return element.style.cssText.toLowerCase();+        },+        title: function(element) {+          return element.title;+        }+      }+    }+  };++  Element._attributeTranslations.write = {+    names: Object.extend({+      cellpadding: 'cellPadding',+      cellspacing: 'cellSpacing'+    }, Element._attributeTranslations.read.names),+    values: {+      checked: function(element, value) {+        element.checked = !!value;+      },++      style: function(element, value) {+        element.style.cssText = value ? value : '';+      }+    }+  };++  Element._attributeTranslations.has = {};++  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' ++      'encType maxLength readOnly longDesc').each(function(attr) {+    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;+    Element._attributeTranslations.has[attr.toLowerCase()] = attr;+  });++  (function(v) {+    Object.extend(v, {+      href:        v._getAttr,+      src:         v._getAttr,+      type:        v._getAttr,+      action:      v._getAttrNode,+      disabled:    v._flag,+      checked:     v._flag,+      readonly:    v._flag,+      multiple:    v._flag,+      onload:      v._getEv,+      onunload:    v._getEv,+      onclick:     v._getEv,+      ondblclick:  v._getEv,+      onmousedown: v._getEv,+      onmouseup:   v._getEv,+      onmouseover: v._getEv,+      onmousemove: v._getEv,+      onmouseout:  v._getEv,+      onfocus:     v._getEv,+      onblur:      v._getEv,+      onkeypress:  v._getEv,+      onkeydown:   v._getEv,+      onkeyup:     v._getEv,+      onsubmit:    v._getEv,+      onreset:     v._getEv,+      onselect:    v._getEv,+      onchange:    v._getEv+    });+  })(Element._attributeTranslations.read.values);+}++else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {+  Element.Methods.setOpacity = function(element, value) {+    element = $(element);+    element.style.opacity = (value == 1) ? 0.999999 :+      (value === '') ? '' : (value < 0.00001) ? 0 : value;+    return element;+  };+}++else if (Prototype.Browser.WebKit) {+  Element.Methods.setOpacity = function(element, value) {+    element = $(element);+    element.style.opacity = (value == 1 || value === '') ? '' :+      (value < 0.00001) ? 0 : value;++    if (value == 1)+      if(element.tagName == 'IMG' && element.width) {+        element.width++; element.width--;+      } else try {+        var n = document.createTextNode(' ');+        element.appendChild(n);+        element.removeChild(n);+      } catch (e) { }++    return element;+  };++  // Safari returns margins on body which is incorrect if the child is absolutely+  // positioned.  For performance reasons, redefine Element#cumulativeOffset for+  // KHTML/WebKit only.+  Element.Methods.cumulativeOffset = function(element) {+    var valueT = 0, valueL = 0;+    do {+      valueT += element.offsetTop  || 0;+      valueL += element.offsetLeft || 0;+      if (element.offsetParent == document.body)+        if (Element.getStyle(element, 'position') == 'absolute') break;++      element = element.offsetParent;+    } while (element);++    return Element._returnOffset(valueL, valueT);+  };+}++if (Prototype.Browser.IE || Prototype.Browser.Opera) {+  // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements+  Element.Methods.update = function(element, content) {+    element = $(element);++    if (content && content.toElement) content = content.toElement();+    if (Object.isElement(content)) return element.update().insert(content);++    content = Object.toHTML(content);+    var tagName = element.tagName.toUpperCase();++    if (tagName in Element._insertionTranslations.tags) {+      $A(element.childNodes).each(function(node) { element.removeChild(node) });+      Element._getContentFromAnonymousElement(tagName, content.stripScripts())+        .each(function(node) { element.appendChild(node) });+    }+    else element.innerHTML = content.stripScripts();++    content.evalScripts.bind(content).defer();+    return element;+  };+}++if ('outerHTML' in document.createElement('div')) {+  Element.Methods.replace = function(element, content) {+    element = $(element);++    if (content && content.toElement) content = content.toElement();+    if (Object.isElement(content)) {+      element.parentNode.replaceChild(content, element);+      return element;+    }++    content = Object.toHTML(content);+    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();++    if (Element._insertionTranslations.tags[tagName]) {+      var nextSibling = element.next();+      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());+      parent.removeChild(element);+      if (nextSibling)+        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });+      else+        fragments.each(function(node) { parent.appendChild(node) });+    }+    else element.outerHTML = content.stripScripts();++    content.evalScripts.bind(content).defer();+    return element;+  };+}++Element._returnOffset = function(l, t) {+  var result = [l, t];+  result.left = l;+  result.top = t;+  return result;+};++Element._getContentFromAnonymousElement = function(tagName, html) {+  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];+  if (t) {+    div.innerHTML = t[0] + html + t[1];+    t[2].times(function() { div = div.firstChild });+  } else div.innerHTML = html;+  return $A(div.childNodes);+};++Element._insertionTranslations = {+  before: function(element, node) {+    element.parentNode.insertBefore(node, element);+  },+  top: function(element, node) {+    element.insertBefore(node, element.firstChild);+  },+  bottom: function(element, node) {+    element.appendChild(node);+  },+  after: function(element, node) {+    element.parentNode.insertBefore(node, element.nextSibling);+  },+  tags: {+    TABLE:  ['<table>',                '</table>',                   1],+    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],+    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],+    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],+    SELECT: ['<select>',               '</select>',                  1]+  }+};++(function() {+  Object.extend(this.tags, {+    THEAD: this.tags.TBODY,+    TFOOT: this.tags.TBODY,+    TH:    this.tags.TD+  });+}).call(Element._insertionTranslations);++Element.Methods.Simulated = {+  hasAttribute: function(element, attribute) {+    attribute = Element._attributeTranslations.has[attribute] || attribute;+    var node = $(element).getAttributeNode(attribute);+    return node && node.specified;+  }+};++Element.Methods.ByTag = { };++Object.extend(Element, Element.Methods);++if (!Prototype.BrowserFeatures.ElementExtensions &&+    document.createElement('div').__proto__) {+  window.HTMLElement = { };+  window.HTMLElement.prototype = document.createElement('div').__proto__;+  Prototype.BrowserFeatures.ElementExtensions = true;+}++Element.extend = (function() {+  if (Prototype.BrowserFeatures.SpecificElementExtensions)+    return Prototype.K;++  var Methods = { }, ByTag = Element.Methods.ByTag;++  var extend = Object.extend(function(element) {+    if (!element || element._extendedByPrototype ||+        element.nodeType != 1 || element == window) return element;++    var methods = Object.clone(Methods),+      tagName = element.tagName, property, value;++    // extend methods for specific tags+    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);++    for (property in methods) {+      value = methods[property];+      if (Object.isFunction(value) && !(property in element))+        element[property] = value.methodize();+    }++    element._extendedByPrototype = Prototype.emptyFunction;+    return element;++  }, {+    refresh: function() {+      // extend methods for all tags (Safari doesn't need this)+      if (!Prototype.BrowserFeatures.ElementExtensions) {+        Object.extend(Methods, Element.Methods);+        Object.extend(Methods, Element.Methods.Simulated);+      }+    }+  });++  extend.refresh();+  return extend;+})();++Element.hasAttribute = function(element, attribute) {+  if (element.hasAttribute) return element.hasAttribute(attribute);+  return Element.Methods.Simulated.hasAttribute(element, attribute);+};++Element.addMethods = function(methods) {+  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;++  if (!methods) {+    Object.extend(Form, Form.Methods);+    Object.extend(Form.Element, Form.Element.Methods);+    Object.extend(Element.Methods.ByTag, {+      "FORM":     Object.clone(Form.Methods),+      "INPUT":    Object.clone(Form.Element.Methods),+      "SELECT":   Object.clone(Form.Element.Methods),+      "TEXTAREA": Object.clone(Form.Element.Methods)+    });+  }++  if (arguments.length == 2) {+    var tagName = methods;+    methods = arguments[1];+  }++  if (!tagName) Object.extend(Element.Methods, methods || { });+  else {+    if (Object.isArray(tagName)) tagName.each(extend);+    else extend(tagName);+  }++  function extend(tagName) {+    tagName = tagName.toUpperCase();+    if (!Element.Methods.ByTag[tagName])+      Element.Methods.ByTag[tagName] = { };+    Object.extend(Element.Methods.ByTag[tagName], methods);+  }++  function copy(methods, destination, onlyIfAbsent) {+    onlyIfAbsent = onlyIfAbsent || false;+    for (var property in methods) {+      var value = methods[property];+      if (!Object.isFunction(value)) continue;+      if (!onlyIfAbsent || !(property in destination))+        destination[property] = value.methodize();+    }+  }++  function findDOMClass(tagName) {+    var klass;+    var trans = {+      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",+      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",+      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",+      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",+      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":+      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":+      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":+      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":+      "FrameSet", "IFRAME": "IFrame"+    };+    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';+    if (window[klass]) return window[klass];+    klass = 'HTML' + tagName + 'Element';+    if (window[klass]) return window[klass];+    klass = 'HTML' + tagName.capitalize() + 'Element';+    if (window[klass]) return window[klass];++    window[klass] = { };+    window[klass].prototype = document.createElement(tagName).__proto__;+    return window[klass];+  }++  if (F.ElementExtensions) {+    copy(Element.Methods, HTMLElement.prototype);+    copy(Element.Methods.Simulated, HTMLElement.prototype, true);+  }++  if (F.SpecificElementExtensions) {+    for (var tag in Element.Methods.ByTag) {+      var klass = findDOMClass(tag);+      if (Object.isUndefined(klass)) continue;+      copy(T[tag], klass.prototype);+    }+  }++  Object.extend(Element, Element.Methods);+  delete Element.ByTag;++  if (Element.extend.refresh) Element.extend.refresh();+  Element.cache = { };+};++document.viewport = {+  getDimensions: function() {+    var dimensions = { };+    var B = Prototype.Browser;+    $w('width height').each(function(d) {+      var D = d.capitalize();+      dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] :+        (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D];+    });+    return dimensions;+  },++  getWidth: function() {+    return this.getDimensions().width;+  },++  getHeight: function() {+    return this.getDimensions().height;+  },++  getScrollOffsets: function() {+    return Element._returnOffset(+      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,+      window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);+  }+};+/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,+ * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style+ * license.  Please see http://www.yui-ext.com/ for more information. */++var Selector = Class.create({+  initialize: function(expression) {+    this.expression = expression.strip();+    this.compileMatcher();+  },++  shouldUseXPath: function() {+    if (!Prototype.BrowserFeatures.XPath) return false;++    var e = this.expression;++    // Safari 3 chokes on :*-of-type and :empty+    if (Prototype.Browser.WebKit &&+     (e.include("-of-type") || e.include(":empty")))+      return false;++    // XPath can't do namespaced attributes, nor can it read+    // the "checked" property from DOM nodes+    if ((/(\[[\w-]*?:|:checked)/).test(this.expression))+      return false;++    return true;+  },++  compileMatcher: function() {+    if (this.shouldUseXPath())+      return this.compileXPathMatcher();++    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,+        c = Selector.criteria, le, p, m;++    if (Selector._cache[e]) {+      this.matcher = Selector._cache[e];+      return;+    }++    this.matcher = ["this.matcher = function(root) {",+                    "var r = root, h = Selector.handlers, c = false, n;"];++    while (e && le != e && (/\S/).test(e)) {+      le = e;+      for (var i in ps) {+        p = ps[i];+        if (m = e.match(p)) {+          this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :+    	      new Template(c[i]).evaluate(m));+          e = e.replace(m[0], '');+          break;+        }+      }+    }++    this.matcher.push("return h.unique(n);\n}");+    eval(this.matcher.join('\n'));+    Selector._cache[this.expression] = this.matcher;+  },++  compileXPathMatcher: function() {+    var e = this.expression, ps = Selector.patterns,+        x = Selector.xpath, le, m;++    if (Selector._cache[e]) {+      this.xpath = Selector._cache[e]; return;+    }++    this.matcher = ['.//*'];+    while (e && le != e && (/\S/).test(e)) {+      le = e;+      for (var i in ps) {+        if (m = e.match(ps[i])) {+          this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :+            new Template(x[i]).evaluate(m));+          e = e.replace(m[0], '');+          break;+        }+      }+    }++    this.xpath = this.matcher.join('');+    Selector._cache[this.expression] = this.xpath;+  },++  findElements: function(root) {+    root = root || document;+    if (this.xpath) return document._getElementsByXPath(this.xpath, root);+    return this.matcher(root);+  },++  match: function(element) {+    this.tokens = [];++    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;+    var le, p, m;++    while (e && le !== e && (/\S/).test(e)) {+      le = e;+      for (var i in ps) {+        p = ps[i];+        if (m = e.match(p)) {+          // use the Selector.assertions methods unless the selector+          // is too complex.+          if (as[i]) {+            this.tokens.push([i, Object.clone(m)]);+            e = e.replace(m[0], '');+          } else {+            // reluctantly do a document-wide search+            // and look for a match in the array+            return this.findElements(document).include(element);+          }+        }+      }+    }++    var match = true, name, matches;+    for (var i = 0, token; token = this.tokens[i]; i++) {+      name = token[0], matches = token[1];+      if (!Selector.assertions[name](element, matches)) {+        match = false; break;+      }+    }++    return match;+  },++  toString: function() {+    return this.expression;+  },++  inspect: function() {+    return "#<Selector:" + this.expression.inspect() + ">";+  }+});++Object.extend(Selector, {+  _cache: { },++  xpath: {+    descendant:   "//*",+    child:        "/*",+    adjacent:     "/following-sibling::*[1]",+    laterSibling: '/following-sibling::*',+    tagName:      function(m) {+      if (m[1] == '*') return '';+      return "[local-name()='" + m[1].toLowerCase() ++             "' or local-name()='" + m[1].toUpperCase() + "']";+    },+    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",+    id:           "[@id='#{1}']",+    attrPresence: function(m) {+      m[1] = m[1].toLowerCase();+      return new Template("[@#{1}]").evaluate(m);+    },+    attr: function(m) {+      m[1] = m[1].toLowerCase();+      m[3] = m[5] || m[6];+      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);+    },+    pseudo: function(m) {+      var h = Selector.xpath.pseudos[m[1]];+      if (!h) return '';+      if (Object.isFunction(h)) return h(m);+      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);+    },+    operators: {+      '=':  "[@#{1}='#{3}']",+      '!=': "[@#{1}!='#{3}']",+      '^=': "[starts-with(@#{1}, '#{3}')]",+      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",+      '*=': "[contains(@#{1}, '#{3}')]",+      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",+      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"+    },+    pseudos: {+      'first-child': '[not(preceding-sibling::*)]',+      'last-child':  '[not(following-sibling::*)]',+      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',+      'empty':       "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",+      'checked':     "[@checked]",+      'disabled':    "[@disabled]",+      'enabled':     "[not(@disabled)]",+      'not': function(m) {+        var e = m[6], p = Selector.patterns,+            x = Selector.xpath, le, v;++        var exclusion = [];+        while (e && le != e && (/\S/).test(e)) {+          le = e;+          for (var i in p) {+            if (m = e.match(p[i])) {+              v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);+              exclusion.push("(" + v.substring(1, v.length - 1) + ")");+              e = e.replace(m[0], '');+              break;+            }+          }+        }+        return "[not(" + exclusion.join(" and ") + ")]";+      },+      'nth-child':      function(m) {+        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);+      },+      'nth-last-child': function(m) {+        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);+      },+      'nth-of-type':    function(m) {+        return Selector.xpath.pseudos.nth("position() ", m);+      },+      'nth-last-of-type': function(m) {+        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);+      },+      'first-of-type':  function(m) {+        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);+      },+      'last-of-type':   function(m) {+        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);+      },+      'only-of-type':   function(m) {+        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);+      },+      nth: function(fragment, m) {+        var mm, formula = m[6], predicate;+        if (formula == 'even') formula = '2n+0';+        if (formula == 'odd')  formula = '2n+1';+        if (mm = formula.match(/^(\d+)$/)) // digit only+          return '[' + fragment + "= " + mm[1] + ']';+        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b+          if (mm[1] == "-") mm[1] = -1;+          var a = mm[1] ? Number(mm[1]) : 1;+          var b = mm[2] ? Number(mm[2]) : 0;+          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " ++          "((#{fragment} - #{b}) div #{a} >= 0)]";+          return new Template(predicate).evaluate({+            fragment: fragment, a: a, b: b });+        }+      }+    }+  },++  criteria: {+    tagName:      'n = h.tagName(n, r, "#{1}", c);      c = false;',+    className:    'n = h.className(n, r, "#{1}", c);    c = false;',+    id:           'n = h.id(n, r, "#{1}", c);           c = false;',+    attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',+    attr: function(m) {+      m[3] = (m[5] || m[6]);+      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);+    },+    pseudo: function(m) {+      if (m[6]) m[6] = m[6].replace(/"/g, '\\"');+      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);+    },+    descendant:   'c = "descendant";',+    child:        'c = "child";',+    adjacent:     'c = "adjacent";',+    laterSibling: 'c = "laterSibling";'+  },++  patterns: {+    // combinators must be listed first+    // (and descendant needs to be last combinator)+    laterSibling: /^\s*~\s*/,+    child:        /^\s*>\s*/,+    adjacent:     /^\s*\+\s*/,+    descendant:   /^\s/,++    // selectors follow+    tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,+    id:           /^#([\w\-\*]+)(\b|$)/,+    className:    /^\.([\w\-\*]+)(\b|$)/,+    pseudo:+/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,+    attrPresence: /^\[([\w]+)\]/,+    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/+  },++  // for Selector.match and Element#match+  assertions: {+    tagName: function(element, matches) {+      return matches[1].toUpperCase() == element.tagName.toUpperCase();+    },++    className: function(element, matches) {+      return Element.hasClassName(element, matches[1]);+    },++    id: function(element, matches) {+      return element.id === matches[1];+    },++    attrPresence: function(element, matches) {+      return Element.hasAttribute(element, matches[1]);+    },++    attr: function(element, matches) {+      var nodeValue = Element.readAttribute(element, matches[1]);+      return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);+    }+  },++  handlers: {+    // UTILITY FUNCTIONS+    // joins two collections+    concat: function(a, b) {+      for (var i = 0, node; node = b[i]; i++)+        a.push(node);+      return a;+    },++    // marks an array of nodes for counting+    mark: function(nodes) {+      var _true = Prototype.emptyFunction;+      for (var i = 0, node; node = nodes[i]; i++)+        node._countedByPrototype = _true;+      return nodes;+    },++    unmark: function(nodes) {+      for (var i = 0, node; node = nodes[i]; i++)+        node._countedByPrototype = undefined;+      return nodes;+    },++    // mark each child node with its position (for nth calls)+    // "ofType" flag indicates whether we're indexing for nth-of-type+    // rather than nth-child+    index: function(parentNode, reverse, ofType) {+      parentNode._countedByPrototype = Prototype.emptyFunction;+      if (reverse) {+        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {+          var node = nodes[i];+          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;+        }+      } else {+        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)+          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;+      }+    },++    // filters out duplicates and extends all nodes+    unique: function(nodes) {+      if (nodes.length == 0) return nodes;+      var results = [], n;+      for (var i = 0, l = nodes.length; i < l; i++)+        if (!(n = nodes[i])._countedByPrototype) {+          n._countedByPrototype = Prototype.emptyFunction;+          results.push(Element.extend(n));+        }+      return Selector.handlers.unmark(results);+    },++    // COMBINATOR FUNCTIONS+    descendant: function(nodes) {+      var h = Selector.handlers;+      for (var i = 0, results = [], node; node = nodes[i]; i++)+        h.concat(results, node.getElementsByTagName('*'));+      return results;+    },++    child: function(nodes) {+      var h = Selector.handlers;+      for (var i = 0, results = [], node; node = nodes[i]; i++) {+        for (var j = 0, child; child = node.childNodes[j]; j++)+          if (child.nodeType == 1 && child.tagName != '!') results.push(child);+      }+      return results;+    },++    adjacent: function(nodes) {+      for (var i = 0, results = [], node; node = nodes[i]; i++) {+        var next = this.nextElementSibling(node);+        if (next) results.push(next);+      }+      return results;+    },++    laterSibling: function(nodes) {+      var h = Selector.handlers;+      for (var i = 0, results = [], node; node = nodes[i]; i++)+        h.concat(results, Element.nextSiblings(node));+      return results;+    },++    nextElementSibling: function(node) {+      while (node = node.nextSibling)+	      if (node.nodeType == 1) return node;+      return null;+    },++    previousElementSibling: function(node) {+      while (node = node.previousSibling)+        if (node.nodeType == 1) return node;+      return null;+    },++    // TOKEN FUNCTIONS+    tagName: function(nodes, root, tagName, combinator) {+      var uTagName = tagName.toUpperCase();+      var results = [], h = Selector.handlers;+      if (nodes) {+        if (combinator) {+          // fastlane for ordinary descendant combinators+          if (combinator == "descendant") {+            for (var i = 0, node; node = nodes[i]; i++)+              h.concat(results, node.getElementsByTagName(tagName));+            return results;+          } else nodes = this[combinator](nodes);+          if (tagName == "*") return nodes;+        }+        for (var i = 0, node; node = nodes[i]; i++)+          if (node.tagName.toUpperCase() === uTagName) results.push(node);+        return results;+      } else return root.getElementsByTagName(tagName);+    },++    id: function(nodes, root, id, combinator) {+      var targetNode = $(id), h = Selector.handlers;+      if (!targetNode) return [];+      if (!nodes && root == document) return [targetNode];+      if (nodes) {+        if (combinator) {+          if (combinator == 'child') {+            for (var i = 0, node; node = nodes[i]; i++)+              if (targetNode.parentNode == node) return [targetNode];+          } else if (combinator == 'descendant') {+            for (var i = 0, node; node = nodes[i]; i++)+              if (Element.descendantOf(targetNode, node)) return [targetNode];+          } else if (combinator == 'adjacent') {+            for (var i = 0, node; node = nodes[i]; i++)+              if (Selector.handlers.previousElementSibling(targetNode) == node)+                return [targetNode];+          } else nodes = h[combinator](nodes);+        }+        for (var i = 0, node; node = nodes[i]; i++)+          if (node == targetNode) return [targetNode];+        return [];+      }+      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];+    },++    className: function(nodes, root, className, combinator) {+      if (nodes && combinator) nodes = this[combinator](nodes);+      return Selector.handlers.byClassName(nodes, root, className);+    },++    byClassName: function(nodes, root, className) {+      if (!nodes) nodes = Selector.handlers.descendant([root]);+      var needle = ' ' + className + ' ';+      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {+        nodeClassName = node.className;+        if (nodeClassName.length == 0) continue;+        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))+          results.push(node);+      }+      return results;+    },++    attrPresence: function(nodes, root, attr, combinator) {+      if (!nodes) nodes = root.getElementsByTagName("*");+      if (nodes && combinator) nodes = this[combinator](nodes);+      var results = [];+      for (var i = 0, node; node = nodes[i]; i++)+        if (Element.hasAttribute(node, attr)) results.push(node);+      return results;+    },++    attr: function(nodes, root, attr, value, operator, combinator) {+      if (!nodes) nodes = root.getElementsByTagName("*");+      if (nodes && combinator) nodes = this[combinator](nodes);+      var handler = Selector.operators[operator], results = [];+      for (var i = 0, node; node = nodes[i]; i++) {+        var nodeValue = Element.readAttribute(node, attr);+        if (nodeValue === null) continue;+        if (handler(nodeValue, value)) results.push(node);+      }+      return results;+    },++    pseudo: function(nodes, name, value, root, combinator) {+      if (nodes && combinator) nodes = this[combinator](nodes);+      if (!nodes) nodes = root.getElementsByTagName("*");+      return Selector.pseudos[name](nodes, value, root);+    }+  },++  pseudos: {+    'first-child': function(nodes, value, root) {+      for (var i = 0, results = [], node; node = nodes[i]; i++) {+        if (Selector.handlers.previousElementSibling(node)) continue;+          results.push(node);+      }+      return results;+    },+    'last-child': function(nodes, value, root) {+      for (var i = 0, results = [], node; node = nodes[i]; i++) {+        if (Selector.handlers.nextElementSibling(node)) continue;+          results.push(node);+      }+      return results;+    },+    'only-child': function(nodes, value, root) {+      var h = Selector.handlers;+      for (var i = 0, results = [], node; node = nodes[i]; i++)+        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))+          results.push(node);+      return results;+    },+    'nth-child':        function(nodes, formula, root) {+      return Selector.pseudos.nth(nodes, formula, root);+    },+    'nth-last-child':   function(nodes, formula, root) {+      return Selector.pseudos.nth(nodes, formula, root, true);+    },+    'nth-of-type':      function(nodes, formula, root) {+      return Selector.pseudos.nth(nodes, formula, root, false, true);+    },+    'nth-last-of-type': function(nodes, formula, root) {+      return Selector.pseudos.nth(nodes, formula, root, true, true);+    },+    'first-of-type':    function(nodes, formula, root) {+      return Selector.pseudos.nth(nodes, "1", root, false, true);+    },+    'last-of-type':     function(nodes, formula, root) {+      return Selector.pseudos.nth(nodes, "1", root, true, true);+    },+    'only-of-type':     function(nodes, formula, root) {+      var p = Selector.pseudos;+      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);+    },++    // handles the an+b logic+    getIndices: function(a, b, total) {+      if (a == 0) return b > 0 ? [b] : [];+      return $R(1, total).inject([], function(memo, i) {+        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);+        return memo;+      });+    },++    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type+    nth: function(nodes, formula, root, reverse, ofType) {+      if (nodes.length == 0) return [];+      if (formula == 'even') formula = '2n+0';+      if (formula == 'odd')  formula = '2n+1';+      var h = Selector.handlers, results = [], indexed = [], m;+      h.mark(nodes);+      for (var i = 0, node; node = nodes[i]; i++) {+        if (!node.parentNode._countedByPrototype) {+          h.index(node.parentNode, reverse, ofType);+          indexed.push(node.parentNode);+        }+      }+      if (formula.match(/^\d+$/)) { // just a number+        formula = Number(formula);+        for (var i = 0, node; node = nodes[i]; i++)+          if (node.nodeIndex == formula) results.push(node);+      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b+        if (m[1] == "-") m[1] = -1;+        var a = m[1] ? Number(m[1]) : 1;+        var b = m[2] ? Number(m[2]) : 0;+        var indices = Selector.pseudos.getIndices(a, b, nodes.length);+        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {+          for (var j = 0; j < l; j++)+            if (node.nodeIndex == indices[j]) results.push(node);+        }+      }+      h.unmark(nodes);+      h.unmark(indexed);+      return results;+    },++    'empty': function(nodes, value, root) {+      for (var i = 0, results = [], node; node = nodes[i]; i++) {+        // IE treats comments as element nodes+        if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;+        results.push(node);+      }+      return results;+    },++    'not': function(nodes, selector, root) {+      var h = Selector.handlers, selectorType, m;+      var exclusions = new Selector(selector).findElements(root);+      h.mark(exclusions);+      for (var i = 0, results = [], node; node = nodes[i]; i++)+        if (!node._countedByPrototype) results.push(node);+      h.unmark(exclusions);+      return results;+    },++    'enabled': function(nodes, value, root) {+      for (var i = 0, results = [], node; node = nodes[i]; i++)+        if (!node.disabled) results.push(node);+      return results;+    },++    'disabled': function(nodes, value, root) {+      for (var i = 0, results = [], node; node = nodes[i]; i++)+        if (node.disabled) results.push(node);+      return results;+    },++    'checked': function(nodes, value, root) {+      for (var i = 0, results = [], node; node = nodes[i]; i++)+        if (node.checked) results.push(node);+      return results;+    }+  },++  operators: {+    '=':  function(nv, v) { return nv == v; },+    '!=': function(nv, v) { return nv != v; },+    '^=': function(nv, v) { return nv.startsWith(v); },+    '$=': function(nv, v) { return nv.endsWith(v); },+    '*=': function(nv, v) { return nv.include(v); },+    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },+    '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }+  },++  split: function(expression) {+    var expressions = [];+    expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {+      expressions.push(m[1].strip());+    });+    return expressions;+  },++  matchElements: function(elements, expression) {+    var matches = $$(expression), h = Selector.handlers;+    h.mark(matches);+    for (var i = 0, results = [], element; element = elements[i]; i++)+      if (element._countedByPrototype) results.push(element);+    h.unmark(matches);+    return results;+  },++  findElement: function(elements, expression, index) {+    if (Object.isNumber(expression)) {+      index = expression; expression = false;+    }+    return Selector.matchElements(elements, expression || '*')[index || 0];+  },++  findChildElements: function(element, expressions) {+    expressions = Selector.split(expressions.join(','));+    var results = [], h = Selector.handlers;+    for (var i = 0, l = expressions.length, selector; i < l; i++) {+      selector = new Selector(expressions[i].strip());+      h.concat(results, selector.findElements(element));+    }+    return (l > 1) ? h.unique(results) : results;+  }+});++if (Prototype.Browser.IE) {+  Object.extend(Selector.handlers, {+    // IE returns comment nodes on getElementsByTagName("*").+    // Filter them out.+    concat: function(a, b) {+      for (var i = 0, node; node = b[i]; i++)+        if (node.tagName !== "!") a.push(node);+      return a;+    },++    // IE improperly serializes _countedByPrototype in (inner|outer)HTML.+    unmark: function(nodes) {+      for (var i = 0, node; node = nodes[i]; i++)+        node.removeAttribute('_countedByPrototype');+      return nodes;+    }+  });+}++function $$() {+  return Selector.findChildElements(document, $A(arguments));+}+var Form = {+  reset: function(form) {+    $(form).reset();+    return form;+  },++  serializeElements: function(elements, options) {+    if (typeof options != 'object') options = { hash: !!options };+    else if (Object.isUndefined(options.hash)) options.hash = true;+    var key, value, submitted = false, submit = options.submit;++    var data = elements.inject({ }, function(result, element) {+      if (!element.disabled && element.name) {+        key = element.name; value = $(element).getValue();+        if (value != null && (element.type != 'submit' || (!submitted &&+            submit !== false && (!submit || key == submit) && (submitted = true)))) {+          if (key in result) {+            // a key is already present; construct an array of values+            if (!Object.isArray(result[key])) result[key] = [result[key]];+            result[key].push(value);+          }+          else result[key] = value;+        }+      }+      return result;+    });++    return options.hash ? data : Object.toQueryString(data);+  }+};++Form.Methods = {+  serialize: function(form, options) {+    return Form.serializeElements(Form.getElements(form), options);+  },++  getElements: function(form) {+    return $A($(form).getElementsByTagName('*')).inject([],+      function(elements, child) {+        if (Form.Element.Serializers[child.tagName.toLowerCase()])+          elements.push(Element.extend(child));+        return elements;+      }+    );+  },++  getInputs: function(form, typeName, name) {+    form = $(form);+    var inputs = form.getElementsByTagName('input');++    if (!typeName && !name) return $A(inputs).map(Element.extend);++    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {+      var input = inputs[i];+      if ((typeName && input.type != typeName) || (name && input.name != name))+        continue;+      matchingInputs.push(Element.extend(input));+    }++    return matchingInputs;+  },++  disable: function(form) {+    form = $(form);+    Form.getElements(form).invoke('disable');+    return form;+  },++  enable: function(form) {+    form = $(form);+    Form.getElements(form).invoke('enable');+    return form;+  },++  findFirstElement: function(form) {+    var elements = $(form).getElements().findAll(function(element) {+      return 'hidden' != element.type && !element.disabled;+    });+    var firstByIndex = elements.findAll(function(element) {+      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;+    }).sortBy(function(element) { return element.tabIndex }).first();++    return firstByIndex ? firstByIndex : elements.find(function(element) {+      return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());+    });+  },++  focusFirstElement: function(form) {+    form = $(form);+    form.findFirstElement().activate();+    return form;+  },++  request: function(form, options) {+    form = $(form), options = Object.clone(options || { });++    var params = options.parameters, action = form.readAttribute('action') || '';+    if (action.blank()) action = window.location.href;+    options.parameters = form.serialize(true);++    if (params) {+      if (Object.isString(params)) params = params.toQueryParams();+      Object.extend(options.parameters, params);+    }++    if (form.hasAttribute('method') && !options.method)+      options.method = form.method;++    return new Ajax.Request(action, options);+  }+};++/*--------------------------------------------------------------------------*/++Form.Element = {+  focus: function(element) {+    $(element).focus();+    return element;+  },++  select: function(element) {+    $(element).select();+    return element;+  }+};++Form.Element.Methods = {+  serialize: function(element) {+    element = $(element);+    if (!element.disabled && element.name) {+      var value = element.getValue();+      if (value != undefined) {+        var pair = { };+        pair[element.name] = value;+        return Object.toQueryString(pair);+      }+    }+    return '';+  },++  getValue: function(element) {+    element = $(element);+    var method = element.tagName.toLowerCase();+    return Form.Element.Serializers[method](element);+  },++  setValue: function(element, value) {+    element = $(element);+    var method = element.tagName.toLowerCase();+    Form.Element.Serializers[method](element, value);+    return element;+  },++  clear: function(element) {+    $(element).value = '';+    return element;+  },++  present: function(element) {+    return $(element).value != '';+  },++  activate: function(element) {+    element = $(element);+    try {+      element.focus();+      if (element.select && (element.tagName.toLowerCase() != 'input' ||+          !['button', 'reset', 'submit'].include(element.type)))+        element.select();+    } catch (e) { }+    return element;+  },++  disable: function(element) {+    element = $(element);+    element.blur();+    element.disabled = true;+    return element;+  },++  enable: function(element) {+    element = $(element);+    element.disabled = false;+    return element;+  }+};++/*--------------------------------------------------------------------------*/++var Field = Form.Element;+var $F = Form.Element.Methods.getValue;++/*--------------------------------------------------------------------------*/++Form.Element.Serializers = {+  input: function(element, value) {+    switch (element.type.toLowerCase()) {+      case 'checkbox':+      case 'radio':+        return Form.Element.Serializers.inputSelector(element, value);+      default:+        return Form.Element.Serializers.textarea(element, value);+    }+  },++  inputSelector: function(element, value) {+    if (Object.isUndefined(value)) return element.checked ? element.value : null;+    else element.checked = !!value;+  },++  textarea: function(element, value) {+    if (Object.isUndefined(value)) return element.value;+    else element.value = value;+  },++  select: function(element, index) {+    if (Object.isUndefined(index))+      return this[element.type == 'select-one' ?+        'selectOne' : 'selectMany'](element);+    else {+      var opt, value, single = !Object.isArray(index);+      for (var i = 0, length = element.length; i < length; i++) {+        opt = element.options[i];+        value = this.optionValue(opt);+        if (single) {+          if (value == index) {+            opt.selected = true;+            return;+          }+        }+        else opt.selected = index.include(value);+      }+    }+  },++  selectOne: function(element) {+    var index = element.selectedIndex;+    return index >= 0 ? this.optionValue(element.options[index]) : null;+  },++  selectMany: function(element) {+    var values, length = element.length;+    if (!length) return null;++    for (var i = 0, values = []; i < length; i++) {+      var opt = element.options[i];+      if (opt.selected) values.push(this.optionValue(opt));+    }+    return values;+  },++  optionValue: function(opt) {+    // extend element because hasAttribute may not be native+    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;+  }+};++/*--------------------------------------------------------------------------*/++Abstract.TimedObserver = Class.create(PeriodicalExecuter, {+  initialize: function($super, element, frequency, callback) {+    $super(callback, frequency);+    this.element   = $(element);+    this.lastValue = this.getValue();+  },++  execute: function() {+    var value = this.getValue();+    if (Object.isString(this.lastValue) && Object.isString(value) ?+        this.lastValue != value : String(this.lastValue) != String(value)) {+      this.callback(this.element, value);+      this.lastValue = value;+    }+  }+});++Form.Element.Observer = Class.create(Abstract.TimedObserver, {+  getValue: function() {+    return Form.Element.getValue(this.element);+  }+});++Form.Observer = Class.create(Abstract.TimedObserver, {+  getValue: function() {+    return Form.serialize(this.element);+  }+});++/*--------------------------------------------------------------------------*/++Abstract.EventObserver = Class.create({+  initialize: function(element, callback) {+    this.element  = $(element);+    this.callback = callback;++    this.lastValue = this.getValue();+    if (this.element.tagName.toLowerCase() == 'form')+      this.registerFormCallbacks();+    else+      this.registerCallback(this.element);+  },++  onElementEvent: function() {+    var value = this.getValue();+    if (this.lastValue != value) {+      this.callback(this.element, value);+      this.lastValue = value;+    }+  },++  registerFormCallbacks: function() {+    Form.getElements(this.element).each(this.registerCallback, this);+  },++  registerCallback: function(element) {+    if (element.type) {+      switch (element.type.toLowerCase()) {+        case 'checkbox':+        case 'radio':+          Event.observe(element, 'click', this.onElementEvent.bind(this));+          break;+        default:+          Event.observe(element, 'change', this.onElementEvent.bind(this));+          break;+      }+    }+  }+});++Form.Element.EventObserver = Class.create(Abstract.EventObserver, {+  getValue: function() {+    return Form.Element.getValue(this.element);+  }+});++Form.EventObserver = Class.create(Abstract.EventObserver, {+  getValue: function() {+    return Form.serialize(this.element);+  }+});+if (!window.Event) var Event = { };++Object.extend(Event, {+  KEY_BACKSPACE: 8,+  KEY_TAB:       9,+  KEY_RETURN:   13,+  KEY_ESC:      27,+  KEY_LEFT:     37,+  KEY_UP:       38,+  KEY_RIGHT:    39,+  KEY_DOWN:     40,+  KEY_DELETE:   46,+  KEY_HOME:     36,+  KEY_END:      35,+  KEY_PAGEUP:   33,+  KEY_PAGEDOWN: 34,+  KEY_INSERT:   45,++  cache: { },++  relatedTarget: function(event) {+    var element;+    switch(event.type) {+      case 'mouseover': element = event.fromElement; break;+      case 'mouseout':  element = event.toElement;   break;+      default: return null;+    }+    return Element.extend(element);+  }+});++Event.Methods = (function() {+  var isButton;++  if (Prototype.Browser.IE) {+    var buttonMap = { 0: 1, 1: 4, 2: 2 };+    isButton = function(event, code) {+      return event.button == buttonMap[code];+    };++  } else if (Prototype.Browser.WebKit) {+    isButton = function(event, code) {+      switch (code) {+        case 0: return event.which == 1 && !event.metaKey;+        case 1: return event.which == 1 && event.metaKey;+        default: return false;+      }+    };++  } else {+    isButton = function(event, code) {+      return event.which ? (event.which === code + 1) : (event.button === code);+    };+  }++  return {+    isLeftClick:   function(event) { return isButton(event, 0) },+    isMiddleClick: function(event) { return isButton(event, 1) },+    isRightClick:  function(event) { return isButton(event, 2) },++    element: function(event) {+      var node = Event.extend(event).target;+      return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node);+    },++    findElement: function(event, expression) {+      var element = Event.element(event);+      if (!expression) return element;+      var elements = [element].concat(element.ancestors());+      return Selector.findElement(elements, expression, 0);+    },++    pointer: function(event) {+      return {+        x: event.pageX || (event.clientX ++          (document.documentElement.scrollLeft || document.body.scrollLeft)),+        y: event.pageY || (event.clientY ++          (document.documentElement.scrollTop || document.body.scrollTop))+      };+    },++    pointerX: function(event) { return Event.pointer(event).x },+    pointerY: function(event) { return Event.pointer(event).y },++    stop: function(event) {+      Event.extend(event);+      event.preventDefault();+      event.stopPropagation();+      event.stopped = true;+    }+  };+})();++Event.extend = (function() {+  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {+    m[name] = Event.Methods[name].methodize();+    return m;+  });++  if (Prototype.Browser.IE) {+    Object.extend(methods, {+      stopPropagation: function() { this.cancelBubble = true },+      preventDefault:  function() { this.returnValue = false },+      inspect: function() { return "[object Event]" }+    });++    return function(event) {+      if (!event) return false;+      if (event._extendedByPrototype) return event;++      event._extendedByPrototype = Prototype.emptyFunction;+      var pointer = Event.pointer(event);+      Object.extend(event, {+        target: event.srcElement,+        relatedTarget: Event.relatedTarget(event),+        pageX:  pointer.x,+        pageY:  pointer.y+      });+      return Object.extend(event, methods);+    };++  } else {+    Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__;+    Object.extend(Event.prototype, methods);+    return Prototype.K;+  }+})();++Object.extend(Event, (function() {+  var cache = Event.cache;++  function getEventID(element) {+    if (element._prototypeEventID) return element._prototypeEventID[0];+    arguments.callee.id = arguments.callee.id || 1;+    return element._prototypeEventID = [++arguments.callee.id];+  }++  function getDOMEventName(eventName) {+    if (eventName && eventName.include(':')) return "dataavailable";+    return eventName;+  }++  function getCacheForID(id) {+    return cache[id] = cache[id] || { };+  }++  function getWrappersForEventName(id, eventName) {+    var c = getCacheForID(id);+    return c[eventName] = c[eventName] || [];+  }++  function createWrapper(element, eventName, handler) {+    var id = getEventID(element);+    var c = getWrappersForEventName(id, eventName);+    if (c.pluck("handler").include(handler)) return false;++    var wrapper = function(event) {+      if (!Event || !Event.extend ||+        (event.eventName && event.eventName != eventName))+          return false;++      Event.extend(event);+      handler.call(element, event);+    };++    wrapper.handler = handler;+    c.push(wrapper);+    return wrapper;+  }++  function findWrapper(id, eventName, handler) {+    var c = getWrappersForEventName(id, eventName);+    return c.find(function(wrapper) { return wrapper.handler == handler });+  }++  function destroyWrapper(id, eventName, handler) {+    var c = getCacheForID(id);+    if (!c[eventName]) return false;+    c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));+  }++  function destroyCache() {+    for (var id in cache)+      for (var eventName in cache[id])+        cache[id][eventName] = null;+  }++  if (window.attachEvent) {+    window.attachEvent("onunload", destroyCache);+  }++  return {+    observe: function(element, eventName, handler) {+      element = $(element);+      var name = getDOMEventName(eventName);++      var wrapper = createWrapper(element, eventName, handler);+      if (!wrapper) return element;++      if (element.addEventListener) {+        element.addEventListener(name, wrapper, false);+      } else {+        element.attachEvent("on" + name, wrapper);+      }++      return element;+    },++    stopObserving: function(element, eventName, handler) {+      element = $(element);+      var id = getEventID(element), name = getDOMEventName(eventName);++      if (!handler && eventName) {+        getWrappersForEventName(id, eventName).each(function(wrapper) {+          element.stopObserving(eventName, wrapper.handler);+        });+        return element;++      } else if (!eventName) {+        Object.keys(getCacheForID(id)).each(function(eventName) {+          element.stopObserving(eventName);+        });+        return element;+      }++      var wrapper = findWrapper(id, eventName, handler);+      if (!wrapper) return element;++      if (element.removeEventListener) {+        element.removeEventListener(name, wrapper, false);+      } else {+        element.detachEvent("on" + name, wrapper);+      }++      destroyWrapper(id, eventName, handler);++      return element;+    },++    fire: function(element, eventName, memo) {+      element = $(element);+      if (element == document && document.createEvent && !element.dispatchEvent)+        element = document.documentElement;++      var event;+      if (document.createEvent) {+        event = document.createEvent("HTMLEvents");+        event.initEvent("dataavailable", true, true);+      } else {+        event = document.createEventObject();+        event.eventType = "ondataavailable";+      }++      event.eventName = eventName;+      event.memo = memo || { };++      if (document.createEvent) {+        element.dispatchEvent(event);+      } else {+        element.fireEvent(event.eventType, event);+      }++      return Event.extend(event);+    }+  };+})());++Object.extend(Event, Event.Methods);++Element.addMethods({+  fire:          Event.fire,+  observe:       Event.observe,+  stopObserving: Event.stopObserving+});++Object.extend(document, {+  fire:          Element.Methods.fire.methodize(),+  observe:       Element.Methods.observe.methodize(),+  stopObserving: Element.Methods.stopObserving.methodize(),+  loaded:        false+});++(function() {+  /* Support for the DOMContentLoaded event is based on work by Dan Webb,+     Matthias Miller, Dean Edwards and John Resig. */++  var timer;++  function fireContentLoadedEvent() {+    if (document.loaded) return;+    if (timer) window.clearInterval(timer);+    document.fire("dom:loaded");+    document.loaded = true;+  }++  if (document.addEventListener) {+    if (Prototype.Browser.WebKit) {+      timer = window.setInterval(function() {+        if (/loaded|complete/.test(document.readyState))+          fireContentLoadedEvent();+      }, 0);++      Event.observe(window, "load", fireContentLoadedEvent);++    } else {+      document.addEventListener("DOMContentLoaded",+        fireContentLoadedEvent, false);+    }++  } else {+    document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");+    $("__onDOMContentLoaded").onreadystatechange = function() {+      if (this.readyState == "complete") {+        this.onreadystatechange = null;+        fireContentLoadedEvent();+      }+    };+  }+})();+/*------------------------------- DEPRECATED -------------------------------*/++Hash.toQueryString = Object.toQueryString;++var Toggle = { display: Element.toggle };++Element.Methods.childOf = Element.Methods.descendantOf;++var Insertion = {+  Before: function(element, content) {+    return Element.insert(element, {before:content});+  },++  Top: function(element, content) {+    return Element.insert(element, {top:content});+  },++  Bottom: function(element, content) {+    return Element.insert(element, {bottom:content});+  },++  After: function(element, content) {+    return Element.insert(element, {after:content});+  }+};++var $continue = new Error('"throw $continue" is deprecated, use "return" instead');++// This should be moved to script.aculo.us; notice the deprecated methods+// further below, that map to the newer Element methods.+var Position = {+  // set to true if needed, warning: firefox performance problems+  // NOT neeeded for page scrolling, only if draggable contained in+  // scrollable elements+  includeScrollOffsets: false,++  // must be called before calling withinIncludingScrolloffset, every time the+  // page is scrolled+  prepare: function() {+    this.deltaX =  window.pageXOffset+                || document.documentElement.scrollLeft+                || document.body.scrollLeft+                || 0;+    this.deltaY =  window.pageYOffset+                || document.documentElement.scrollTop+                || document.body.scrollTop+                || 0;+  },++  // caches x/y coordinate pair to use with overlap+  within: function(element, x, y) {+    if (this.includeScrollOffsets)+      return this.withinIncludingScrolloffsets(element, x, y);+    this.xcomp = x;+    this.ycomp = y;+    this.offset = Element.cumulativeOffset(element);++    return (y >= this.offset[1] &&+            y <  this.offset[1] + element.offsetHeight &&+            x >= this.offset[0] &&+            x <  this.offset[0] + element.offsetWidth);+  },++  withinIncludingScrolloffsets: function(element, x, y) {+    var offsetcache = Element.cumulativeScrollOffset(element);++    this.xcomp = x + offsetcache[0] - this.deltaX;+    this.ycomp = y + offsetcache[1] - this.deltaY;+    this.offset = Element.cumulativeOffset(element);++    return (this.ycomp >= this.offset[1] &&+            this.ycomp <  this.offset[1] + element.offsetHeight &&+            this.xcomp >= this.offset[0] &&+            this.xcomp <  this.offset[0] + element.offsetWidth);+  },++  // within must be called directly before+  overlap: function(mode, element) {+    if (!mode) return 0;+    if (mode == 'vertical')+      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /+        element.offsetHeight;+    if (mode == 'horizontal')+      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /+        element.offsetWidth;+  },++  // Deprecation layer -- use newer Element methods now (1.5.2).++  cumulativeOffset: Element.Methods.cumulativeOffset,++  positionedOffset: Element.Methods.positionedOffset,++  absolutize: function(element) {+    Position.prepare();+    return Element.absolutize(element);+  },++  relativize: function(element) {+    Position.prepare();+    return Element.relativize(element);+  },++  realOffset: Element.Methods.cumulativeScrollOffset,++  offsetParent: Element.Methods.getOffsetParent,++  page: Element.Methods.viewportOffset,++  clone: function(source, target, options) {+    options = options || { };+    return Element.clonePosition(target, source, options);+  }+};++/*--------------------------------------------------------------------------*/++if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){+  function iter(name) {+    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";+  }++  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?+  function(element, className) {+    className = className.toString().strip();+    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);+    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];+  } : function(element, className) {+    className = className.toString().strip();+    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);+    if (!classNames && !className) return elements;++    var nodes = $(element).getElementsByTagName('*');+    className = ' ' + className + ' ';++    for (var i = 0, child, cn; child = nodes[i]; i++) {+      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||+          (classNames && classNames.all(function(name) {+            return !name.toString().blank() && cn.include(' ' + name + ' ');+          }))))+        elements.push(Element.extend(child));+    }+    return elements;+  };++  return function(className, parentElement) {+    return $(parentElement || document.body).getElementsByClassName(className);+  };+}(Element.Methods);++/*--------------------------------------------------------------------------*/++Element.ClassNames = Class.create();+Element.ClassNames.prototype = {+  initialize: function(element) {+    this.element = $(element);+  },++  _each: function(iterator) {+    this.element.className.split(/\s+/).select(function(name) {+      return name.length > 0;+    })._each(iterator);+  },++  set: function(className) {+    this.element.className = className;+  },++  add: function(classNameToAdd) {+    if (this.include(classNameToAdd)) return;+    this.set($A(this).concat(classNameToAdd).join(' '));+  },++  remove: function(classNameToRemove) {+    if (!this.include(classNameToRemove)) return;+    this.set($A(this).without(classNameToRemove).join(' '));+  },++  toString: function() {+    return $A(this).join(' ');+  }+};++Object.extend(Element.ClassNames.prototype, Enumerable);++/*--------------------------------------------------------------------------*/++Element.addMethods();
+ src/Hayoo/FunctionInfo.hs view
@@ -0,0 +1,62 @@+{-# OPTIONS #-}++-- ------------------------------------------------------------++module Hayoo.FunctionInfo+    ( FunctionInfo(..)+    , mkFunctionInfo+    )+where+import           Control.DeepSeq+import           Control.Monad                  ( liftM5 )++import           Data.Binary                    ( Binary(..) )+import qualified Data.Binary                    as B++import           Text.XML.HXT.Core++-- ------------------------------------------------------------++-- | Additional information about a function.++data FunctionInfo+    = FunctionInfo +      { moduleName  :: String               -- ^ The name of the module containing the function, e.g. Data.Map+      , signature   :: String               -- ^ The full signature of the function, e.g. Ord a => a -> Int -> Bool+      , package     :: String               -- ^ The name of the package containing the module, e.g. containers+      , sourceURI   :: String               -- ^ An optional URI to the online source of the function.+      , fctDescr    :: String               -- ^ The haddock description of a type or function, maybe shortened for space efficiency+      } +    deriving (Show, Eq)++mkFunctionInfo                  :: String -> String -> String -> String -> String -> FunctionInfo+mkFunctionInfo                  = FunctionInfo++instance XmlPickler FunctionInfo where+    xpickle                     = xpWrap (fromTuple, toTuple) xpFunction+        where+        fromTuple (m, s, p, r, d)+                                = FunctionInfo m s p r d+        toTuple (FunctionInfo m s p r d)+                                = (m, s, p, r, d)++        xpFunction              = xp5Tuple xpModule xpSignature xpPackage xpSource xpDescr+            where                                                       -- We are inside a doc-element, and everything is stored as attribute.+            xpModule            = xpAttr "module"    xpText0+            xpSignature         = xpAttr "signature" xpText0+            xpPackage           = xpAttr "package"   xpText0+            xpSource            = xpAttr "source"    xpText0+            xpDescr             = xpAttr "descr"     xpText0++instance NFData FunctionInfo where+  rnf (FunctionInfo m s p r d)  = rnf m `seq` rnf s `seq` rnf p `seq` rnf r `seq` rnf d++instance B.Binary FunctionInfo where+    put (FunctionInfo m s p r d)+                                = put m >> put s >> put p >> put r >> put d+    get                         = do+                                  r <- liftM5 FunctionInfo get get get get get+                                  rnf r `seq`+                                      return r++-- ------------------------------------------------------------
+ src/Hayoo/HackagePackage.hs view
@@ -0,0 +1,153 @@+module Hayoo.HackagePackage+where++import           Data.List++import           Hayoo.URIConfig+import           Hayoo.PackageInfo++import           Holumbus.Crawler.Html++import           Text.XML.HXT.Core+  +import           Text.Regex.XMLSchema.String++-- ------------------------------------------------------------++hayooGetPkgInfo                 :: IOSArrow XmlTree PackageInfo+hayooGetPkgInfo                 = fromLA $+                                  ( getPkgNameAndVersion+                                    &&&+                                    getPkgDependencies+                                    &&&+                                    getPkgAuthor+                                    &&&+                                    getPkgMaintainer+                                    &&&+                                    getPkgCategory+                                    &&&+                                    getPkgHomepage+                                    &&&+                                    getPkgSynopsis+                                    &&&+                                    getPkgDescr+                                  )+                                  >>^+                                  (\ ((x1, x2), (x3, (x4, (x5, (x6, (x7, (x8, x9))))))) -> mkPackageInfo x1 x2 x3 x4 x5 x6 x7 x8 x9)++hayooGetPkgTitle                :: IOSArrow XmlTree String+hayooGetPkgTitle                = fromLA $+                                  getPkgName++-- ------------------------------------------------------------++getPkgName                      :: LA XmlTree String+getPkgName                      = getPkgNameAndVersion >>^ fst++getPkgNameAndVersion            :: LA XmlTree (String, String)+getPkgNameAndVersion            = getHtmlTitle+                                  >>^+                                  ( ( words >>> drop 1 >>> take 1 >>> unwords )+                                    >>>+                                    ( sed (const "") packageVersion''+                                      &&&+                                      ( tokenize packageVersion'' >>> reverse >>> take 1 >>> concat >>> drop 1)+                                    )+                                  )++getPkgDependencies              :: LA XmlTree [String]+getPkgDependencies              = getProperty "Dependencies"+                                  >>>+                                  listA+                                  ( getChildren+                                    >>>+                                    hasName "a"+                                    >>>+                                    getChildren+                                    >>>+                                    getText+                                    >>>+                                    this -- checkPackageName+                                  )+                                  >>>+                                  arr (sort >>> nub)++getPkgAuthor                    :: LA XmlTree String+getPkgAuthor                    = getAllText $ getProperty "Author(s)?"++getPkgMaintainer                :: LA XmlTree String+getPkgMaintainer                = getAllText $ getProperty "Maintainer(s)?"++getPkgCategory                  :: LA XmlTree String+getPkgCategory                  = getAllText $ getProperty "Category"++getPkgHomepage                  :: LA XmlTree String+getPkgHomepage                  = getAllText $ getProperty "Home page"++getPkgSynopsis                  :: LA XmlTree String+getPkgSynopsis                  = ( getAllText+                                    ( getByPath ["html","body"]+                                      />+                                      isElemWithAttr "div" "id" (== "package-header")+                                      />+                                      isElemWithAttr "p" "class" (== "caption")+                                    )+                                  )+                                  >>^+                                  ( dropWhile (/= ':') >>> drop 1 >>> dropWhile (== ' '))       -- remove package name++getPkgDescr                     :: LA XmlTree String+getPkgDescr                     = getAllText                    -- take all stuff between "h1" and next "h2" element in content+                                  ( ( getByPath ["html","body"]+                                      />+                                      isElemWithAttr "div" "id" (== "content")+                                    )+                                    >>>+                                    listA getChildren+                                    >>>+                                    spanA (neg $ hasName "h2")+                                    >>>+                                    arr fst+                                    >>>+                                    unlistA+                                    >>>+                                    (none `when` hasName "h1")+                                  )++-- ------------------------------------------------------------++preparePkg                      :: IOSArrow XmlTree XmlTree+preparePkg                      = fromLA $+                                  isHackagePackage++isHackagePackage                :: LA XmlTree XmlTree+isHackagePackage                = hasAttrValue transferURI (match $ hackagePackages ++ fileName)++-- ------------------------------------------------------------++getProperties           :: LA XmlTree XmlTree+getProperties           = single (deep (hasName "table"))               -- there should be only a single table+                                                                        -- old package layout:+                                                                        -- deep ( isElemWithAttr "table" "class" (== "properties") )+                          />+                          hasName "tr"++getProperty             :: String -> LA XmlTree XmlTree+getProperty kw          = getProperties+                          >>>+                          ( ( getChildren+                              >>>+                              hasName "th"+                              >>>+                              ( getChildren >>. take 1 )+                              >>>+                              hasText (match kw)+                            )+                            `guards`+                            ( getChildren+                              >>>+                              hasName "td"+                            )+                          )++-- ------------------------------------------------------------
+ src/Hayoo/Haddock.hs view
@@ -0,0 +1,872 @@+{-# OPTIONS #-}++-- ------------------------------------------------------------++module Hayoo.Haddock+    ( hayooGetFctInfo+    , hayooGetTitle+    , hayooGetDescr+    , prepareHaddock+    )+where++import Data.List+import Data.Maybe++import Hayoo.URIConfig+import Hayoo.FunctionInfo+import Hayoo.Signature++import Holumbus.Crawler.Html+import Holumbus.Utility++import Network.URI                      ( unEscapeString )++import Text.Regex.XMLSchema.String      ( match+                                        , tokenize+                                        )++import Text.XML.HXT.Core     +import Text.XML.HXT.Arrow.XmlRegex+import Text.XML.HXT.XPath++-- ------------------------------------------------------------++hayooGetFctInfo                 :: IOSArrow XmlTree FunctionInfo+hayooGetFctInfo                 = fromLA $+                                  ( getAttrValue "module"+                                    &&&+                                    getAttrValue "signature"+                                    &&&+                                    getAttrValue "package"+                                    &&&+                                    getAttrValue "source"+                                    &&&+                                    xshow+                                    ( hayooGetDescr+                                      >>>+                                      getChildren+                                      >>>+                                      editDescrMarkup+                                    )+                                  )+                                  >>^+                                  (\ (m, (s, (p, (r, d)))) -> mkFunctionInfo m s p r d)++hayooGetTitle                   :: IOSArrow XmlTree String+hayooGetTitle                   = fromLA $+                                  getAttrValue "title"++hayooGetDescr                   :: LA XmlTree XmlTree+hayooGetDescr                   = ifA version28+                                  ( getChildren                                 -- 2.8+                                    >>>+                                    divWithClass (== "top")+                                    >>>+                                    firstChildWithClass "div" "doc"+                                  )+                                  ( deep (trtdWithClass (== "doc")) )           -- 2.6++editDescrMarkup                 :: LA XmlTree XmlTree+editDescrMarkup                 = processTopDown+                                  ( remHref `when` hasName "a" )+    where+    remHref                     = processAttrl (none `when` hasName "href")++version28                       :: LA XmlTree XmlTree+version28                       = hasAttrValue "version" (== "2.8")++-- ------------------------------------------------------------++prepareHaddock                  :: IOSArrow XmlTree XmlTree+prepareHaddock                  = ( traceMsg 1 "prepareHaddock: try version 2.8"+                                    >>>+                                    prepareHaddock28+                                  )+                                  `orElse`+                                  ( traceMsg 1 "prepareHaddock: try version 2.6"+                                    >>>+                                    prepareHaddock26+                                  )+                                  `orElse`+                                  ( traceMsg 1 "prepareHaddock: no haddock" )++-- ------------------------------------------------------------++isHaddock28                     :: LA XmlTree XmlTree+isHaddock28                     = getPath "html/body/div/p"+                                  >>>+                                  ( hasAElemWithHaddock+                                    `guards`+                                    hasVersionGE28+                                  )+    where+    hasAElemWithHaddock         = this+                                  /> hasName "a"+                                  /> hasText (== "Haddock")+    hasVersionGE28              = this+                                  /> hasText matchVersionGE28++matchVersionGE28                :: String -> Bool+matchVersionGE28                = match ".* [2-9][.]([1-9][0-9]+|[8-9])([.][0-9]+)*"++prepareHaddock28                :: IOSArrow XmlTree XmlTree+prepareHaddock28                = fromLA $+                                  seqA+                                  [ this+                                  , ( isHaddock28 `guards` this )+                                  , addPackageAttr+                                  , addModuleAttr+                                  , splitHaddock28+                                  ]+                                  -- >>> withTraceLevel 4 (traceDoc "result of splitHaddock28") -- just for dev.++splitHaddock28                  :: LA XmlTree XmlTree+splitHaddock28                  = mkVirtualDoc28 $< this++mkVirtualDoc28                  :: XmlTree -> LA XmlTree XmlTree+mkVirtualDoc28 rt               = (getModule <+> getDecls)+                                  >>>+                                  mkDoc+    where+    mkDoc                       = ( root [] []+                                    += attr "title"     (theTitle     >>> mkText)+                                    += attr "module"    (theModule    >>> mkText)+                                    += attr "package"   (thePackage   >>> mkText)+                                    += attr "signature" (theSignature >>> mkText)+                                    += attr "source"    (theSourceURI >>> mkText)+                                    += sattr "version"  "2.8"+                                    += attr transferURI ( ( (theURI &&& theLinkPrefix &&& theTitle)+                                                            >>^+                                                            (\ (u, (h, t)) -> u ++ h ++ t)+                                                          )+                                                          >>>+                                                          mkText+                                                        )+                                    += removeSourceLinks+                                  )++    getModule                   =  mkModuleDecl+                                   ( ( eelem "a"+                                       += sattr "class" "def"+                                       += theModuleName+                                     )+                                     <+>+                                     theSourceLink+                                   )+                                   theModuleDoc+        where+        theModuleName           = xshow+                                  ( getById "module-header"                     -- single (deep (isElemWithAttr "div" "id" (== "module-header")))+                                    /> pWithClass (== "caption")+                                    /> isText+                                  )+                                  >>>+                                  arr (tokenize "[^.]+" >>> last)                -- A will be the name of module C.B.A+                                  >>>+                                  mkText++        theSourceLink           = getById "package-header"                      -- single (deep (isElemWithAttr "div" "id" (== "package-header")))+                                  /> getById "page-menu"                        -- isElemWithAttr "ul" "id" (== "page-menu")+                                  /> hasName "li"+                                  /> hasName "a"+                                  >>> ( (getChildren >>> hasText (== "Source"))+                                        `guards`+                                        ( this += sattr "class" "link" )+                                      )++        theModuleDoc            = getById "description"                         -- single (deep (isElemWithAttr "div" "id" (== "description")))+                                  />+                                  divWithClass (== "doc")++    getDecls                    = getById "interface"                           -- this //> isElemWithAttr "div" "id" (== "interface")+                                  />  divWithClass (== "top")+                                  >>> choiceA+                                      [ isDataTypeNewtypeDecl+                                                       :-> ( processTypeDecl+                                                             <+>+                                                             ( processConstructors $< getSrcLnk ) -- the data sourrce link is propagated+                                                           )                                      -- to the constructors and fields+                                      , isClassDecl    :-> ( processClassDecl+                                                             <+>+                                                             processMethods+                                                           )+                                      , isFunctionDecl :-> processFunctionDiv+                                      , this           :-> none+                                      ]++    getSrcLnk                   = ( first_p_src+                                    >>>+                                    firstChildWithClass "a" "link"+                                  )+                                  `orElse`+                                  txt ""++    isFunctionDecl              = first_p_src+                                  >>>+                                  firstChild (aWithClass (== "def")+                                              >>>+                                              hasAttr "name"+                                             )++    isClassDecl                 = isTDecl (== "class")++    isDataTypeNewtypeDecl       = isTDecl (`elem` ["data", "type", "newtype"])++    isTDecl p                   = first_p_src                                   -- check the first p element found+                                  >>>+                                  firstChildWithClass "span" "keyword"          -- check the first keyword found+                                  />+                                  hasText p++    processFunctionDiv         = (mkSingleDiv $< splitMultiDiv)+                                 += sattr "type" "function"+        where+          mkSingleDiv ts        = replaceChildren (constL ts)+          splitMultiDiv         = listA getChildren+                                  >>> arr (head &&& tail)+                                  >>> first processFunctionSig+                                  >>> arr (uncurry (:))++    processFunctionSig          = mkSingleFct $< splitMultiFct+        where+          mkSingleFct ts        = replaceChildren (constL ts)+          splitMultiFct         = listA getChildren+                                  >>> spanA (neg $ hasText ((== "::") . stringTrim))+                                  >>> first ( unlistA+                                              >>> aWithClass (== "def")+                                              >>> hasAttr "name"+                                            ) +                                  >>> arr (uncurry (:))++    processTypeDecl             = this +=  attr "type" theType++    processConstructors srcLnk  = this+                                  />  divWithClass (words >>> ("constructors" `elem`))+                                  />  hasName "table"+                                  />  hasName "tr"+                                  >>> ( (isConstrRow `guards` theConstructors srcLnk)+                                        <+>+                                        processConstrFields srcLnk+                                      )+    isConstrRow                 = matchRegexA ( mkSeq+                                                (mkPrimA $ tdWithClass (== "src"))+                                                (mkPrimA $ tdWithClass (words >>> ("doc" `elem`)))+                                              )+                                  getChildren+                                  >>>+                                  unlistA++    processConstrFields srcLnk  = getChildren+                                  >>>+                                  hasName "td"+                                  />+                                  divWithClass (words >>> ("fields" `elem`))+                                  />+                                  hasName "dl"+                                  >>>+                                  scanRegexA +                                      ( mkSeq (mkPrimA $ hasName "dt") (mkPrimA $ hasName "dd"))+                                      getChildren+                                  >>>+                                  theConstrFields srcLnk++    processClassDecl            = ( this += sattr "type" "class" )+                                  >>>+                                  processChildren ( pWithClass   (== "src")+                                                    <+>+                                                    divWithClass (== "doc")+                                                  )++    processMethods              = getChildren+                                  >>>+                                  divWithClass (words >>> ("methods" `elem`))+                                  >>>+                                  scanRegexA+                                    ( mkSeq+                                      (        mkPrimA $ hasName "p")+                                      (mkOpt $ mkPrimA $ hasName "div")+                                    )+                                    ( getChildren+                                      >>>+                                      ( pWithClass   (== "src")+                                        <+>+                                        divWithClass (words >>> ("doc" `elem`))+                                      )+                                    )+                                  >>>+                                  theMethods++    theType                     = single (getPath "p/span" /> isText)++    theTitle                    = xshow ( first_p_src+                                          >>>+                                          firstChildWithClass "a" "def"+                                          />+                                          isText+                                        )+                                  >>^+                                  unEscapeString++    theConstructors srcLnk      = mkFctDecl+                                  ( ( getChildren+                                      >>>+                                      tdWithClass (== "src")+                                      >>>+                                      getChildren+                                    )+                                    <+>+                                    constA srcLnk+                                  )+                                  ( getChildren+                                    >>>+                                    tdWithClass (== "doc")+                                    >>>+                                    getChildren+                                  )++    theConstrFields srcLnk      = mkFctDecl+                                  ( (unlistA >>> hasName "dt" >>> getChildren)+                                    <+>+                                    constA srcLnk+                                  )+                                  ( unlistA >>> hasName "dd" >>> getChildren )++    theMethods                  = mkDecl0 "function" ( this >>> unlistA )++    theLinkPrefix               = ( first_p_src+                                    >>>+                                    firstChildWithClass "a" "def"+                                    >>>+                                    getAttrValue "name"+                                    >>^+                                    (take 2 >>> ('#' :))+                                  )+                                  `withDefault` "#v:"++    theSignature                = ( ifA+                                    ( hasAttrValue "type" (`elem` ["function"]) )+                                    ( xshow ( ( single ( this /> pWithClass (== "src") )+                                                <+>+                                                theSubArguments         -- fancy arguments+                                              )+                                              >>>+                                              removeSourceLinks+                                              >>>+                                              deep isText+                                            )+                                    )+                                    ( getAttrValue "type" )+                                  )+                                  >>^+                                  getSignature++    theSubArguments             = getChildren+                                  >>>+                                  divWithClass (words >>> ("arguments" `elem`))+                                  />+                                  hasName "table"+                                  />+                                  hasName "tr"+                                  />+                                  tdWithClass (== "src")+                                  >>>+                                  getChildren+                                  +    theSourceURI                = ( first_p_src+                                    >>>+                                    firstChildWithClass "a" "link"+                                    >>>+                                    getAttrValue       "href"+                                  )+                                  `withDefault` ""++    theModule                   = constA rt >>> getAttrValue "module"+    thePackage                  = constA rt >>> getAttrValue "package"+    theURI                      = constA rt >>> getAttrValue transferURI++-- ------------------------------------------------------------++isHaddock26                     :: LA XmlTree XmlTree+isHaddock26                     = getPath "html/body/table/tr"+                                  /> tdWithClass (== "botbar")+                                  /> hasName "a"+                                  /> hasText (== "Haddock")++prepareHaddock26                :: IOSArrow XmlTree XmlTree+prepareHaddock26                = process+                                  [ this+                                  , ( isHaddock26 `guards` this )+                                  , addPackageAttr+                                  , addModuleAttr+                                  , processClasses                              -- .4+                                  , topdeclToDecl                               -- .5+                                  , removeDataDocumentation                     -- .6+                                  , processDataTypeAndNewtypeDeclarations       -- .7+                                  , processCrazySignatures+                                  , splitHaddock26+                                  ]+    where+    process                     = seqA . zipWith phase [(0::Int)..]+    phase _i f                  = fromLA f+                                  -- >>>+                                  -- traceDoc ("prepare haddock-2.6: step " ++ show i)++splitHaddock26                  :: LA XmlTree XmlTree+splitHaddock26                  = mkVirtualDoc26 $< this++mkVirtualDoc26                  :: XmlTree -> LA XmlTree XmlTree+mkVirtualDoc26 rt               = getDecls+                                  >>>+                                  ( root [] []+                                    += attr "title"     (theTitle     >>> mkText)+                                    += attr "module"    (theModule    >>> mkText)+                                    += attr "package"   (thePackage   >>> mkText)+                                    += attr "signature" (theSignature >>> mkText)+                                    += attr "source"    (theSourceURI >>> mkText)+                                    += sattr "version"  "2.6"+                                    += attr transferURI ( ( (theURI &&& theLinkPrefix &&& theTitle)+                                                            >>^+                                                            (\ (u, (h, t)) -> u ++ h ++ t)+                                                          )+                                                          >>>+                                                          mkText+                                                        )+                                    += removeSourceLinks+                                  )+    where+    getDecls                    = deep ( isDecl' >>> hasAttr "id" )++    isDecl'                     = trWithClass (== "decl")++    theTitle                    = ( listA (isDecl' >>> getAttrValue "id") >>. concat )+                                  >>^+                                  unEscapeString++    theSignature                = xshow ( removeSourceLinks+                                          >>>+                                          deep (tdWithClass (== "decl"))+                                          >>>+                                          deep isText+                                        )+                                  >>^+                                  getSignature++    theLinkPrefix               = theSignature+                                  >>^+                                  ( words+                                    >>>+                                    take 1+                                    >>>+                                    concat+                                    >>>+                                    (\ s -> if s `elem` ["data", "type", "newtype"]+                                            then "#t:"+                                            else "#v:"+                                    )+                                  )+    theSourceURI                = ( single+                                    ( ( deep ( ( aWithHref ("src/" `isPrefixOf`)+                                                 />+                                                 hasText (== "Source")+                                               )+                                               `guards` this+                                             )+                                        >>>+                                        getAttrValue0 "href"+                                      )+                                      &&&+                                      theURI+                                    )+                                    >>^ (uncurry expandURIString >>> fromMaybe "")+                                  )+                                  `withDefault` ""++    theModule                   = constA rt >>> getAttrValue "module"+    thePackage                  = constA rt >>> getAttrValue "package"+    theURI                      = constA rt >>> getAttrValue transferURI++-- ------------------------------------------------------------+ +-- | Transform classes so that the methods are wrapped into the same html as normal functions++processClasses                  :: LA XmlTree XmlTree+processClasses                  = processTopDown+                                  ( processClassMethods+                                    `when`+                                    ( getClassPart "section4"+                                      /> hasText (== "Methods")+                                    )+                                  )+    where   +    processClassMethods         = getClassPart "body"+                                  /> hasName "table"+                                  /> hasName "tr"+                                -- getXPathTrees "/tr/td[@class='body']/table/tr/td[@class='body']/table/tr" ++-- ------------------------------------------------------------++-- | Removes Source Links from the XmlTree. A Source Link can be identified by the text of an "a" +--   node but to be more precise it is also checked whether the href-attribute starts with "src".+--   During the tree transformation it might happen, that source links with empty href attributes +--   are constructed so empty href attributes are also searched and removed if the text of the "a"+--   node is "Source"++removeSourceLinks               :: LA XmlTree XmlTree+removeSourceLinks               = processTopDown+                                  ( none+                                    `when` +                                    ( aWithHref (\a -> null a || "src/" `isPrefixOf` a)+                                      />+                                      hasText (== "Source")+                                    )+                                  )      ++-- ------------------------------------------------------------++-- | As Haddock can generate Documentation pages with links to source files and without these links+--   there are two different types of declaration table datas. To make the indexing easier, the+--   table datas with source links are transformed to look like those without (they differ +--   in the css class of the table data and the ones with the source links contain another table).++topdeclToDecl                   :: LA XmlTree XmlTree+topdeclToDecl                   = processTopDownUntil+                                  ( isElemWithAttr "table" "class" (== "declbar")+                                   `guards`+                                   ( getChildren >>> getChildren >>> getChildren )+                                  )+                                  >>>+                                  processTopDownUntil+                                  ( tdWithClass (== "topdecl")+                                    `guards`+                                    mkelem "td" [ sattr "class" "decl"] [ getChildren ]+                                  ) ++-- ------------------------------------------------------------++removeDataDocumentation         :: LA XmlTree XmlTree+removeDataDocumentation         = processTopDown +                                  ( none+                                    `when`+                                    ( getClassPart "section4"+                                      /> hasText (\a -> a == "Constructors"+                                                        || "Instances" `isSuffixOf` a+                                                 )+                                    )+                                  )++-- ------------------------------------------------------------++processDataTypeAndNewtypeDeclarations :: LA XmlTree XmlTree+processDataTypeAndNewtypeDeclarations +                                = processTopDownUntil+                                  ( (    tdWithClass   (=="decl")+                                      /> spanWithClass (=="keyword")+                                      /> hasText (`elem` ["data", "type", "newtype", "class"]) +                                    )+                                    `guards`+                                    ( mkTheElem $<<<< (     getTheName+                                                        &&& getTheType+                                                        &&& getTheRef+                                                        &&& getTheSrc+                                                      )+                                    ) +                                  )+    where+    getTheName                  = xshow $+                                  deep (hasName "b") /> isText++    getTheRef                   = ( single $+                                    deep (hasName "a" >>> getAttrValue0 "name")+                                  )+                                  `withDefault` ""++    getTheType                  = xshow $+                                  single $+                                  deep (spanWithClass (== "keyword")) /> isText++    getTheSrc                   = ( single $+                                    deep (aWithHref ("src/" `isPrefixOf`))+                                    >>>+                                    getAttrValue0 "href"+                                  )+                                  `withDefault` ""++    mkTheElem n t r s           = eelem "td"+                                  +=   sattr "class" "decl"+                                  += ( eelem "a"+                                       +=   sattr "name" r+                                       +=   txt (n ++ " :: " ++ t)+                                     )+                                  += ( eelem "a"+                                       += sattr "href" s+                                       += txt "Source"+                                     )++-- ------------------------------------------------------------++processCrazySignatures          :: LA XmlTree XmlTree+processCrazySignatures          = processTopDown+                                  ( preProcessCrazySignature+                                    `when`+                                    getClassPart "rdoc"+                                  )+                                  >>>+                                  processChildren+                                  ( processDocumentRootElement groupDeclSig declAndDocAndSignatureChildren )++preProcessCrazySignature        :: LA XmlTree XmlTree+preProcessCrazySignature        = ( selem "tr" +                                    [ mkelem "td" [ sattr "class" "signature" ]+                                                  [ deep (tdWithClass (== "arg"))+                                                    >>>+                                                    getChildren+                                                  ]  +                                    ] +                                    &&&   +                                    selem "tr" +                                    [ mkelem "td" [ sattr "class" "doc" ]+                                                  [ deep (tdWithClass (== "rdoc"))+                                                    >>>+                                                    getChildren+                                                  ]+                                    ]+                                  )+                                  >>> mergeA (<+>)+         +processDocumentRootElement      :: (LA XmlTree XmlTree -> LA XmlTree XmlTree)+                                ->  LA XmlTree XmlTree+                                ->  LA XmlTree XmlTree+processDocumentRootElement theGrouper interestingChildren+                                = processTopDownUntil+                                  ( hasName "table"+                                    `guards`+                                    ( replaceChildren+                                      ( processTableRows theGrouper (getChildren >>> interestingChildren) )+                                    )+                                  )+           +declAndDocAndSignatureChildren :: LA XmlTree XmlTree+declAndDocAndSignatureChildren = (isDecl <+> isSig <+> isDoc) `guards` this++isDecl                          :: LA XmlTree XmlTree+isDecl                          = trtdWithClass (== "decl")+                                  />+                                  isElemWithAttr "a" "name" (const True)++isDoc                           :: LA XmlTree XmlTree+isDoc                           = trtdWithClass (== "doc")+      +isSig                           :: LA XmlTree XmlTree+isSig                           = trtdWithClass (== "signature")++getDeclName                     :: LA XmlTree String+getDeclName                     = (xshow $ single $ getXPathTrees "//tr/td/a/@name/text()") >>^ drop 2++processTableRows                :: (LA XmlTree XmlTree -> LA XmlTree XmlTree)+                                ->  LA XmlTree XmlTree+                                ->  LA XmlTree XmlTree+processTableRows theGrouping ts = theGrouping (remLeadingDocElems ts) {- >>> prune 3 -}++-- regex for a leading class="doc" row++leadingDoc                      :: XmlRegex+leadingDoc                      = mkStar (mkPrimA isDoc)++-- regex for a class="decl" class="doc" sequence++declSig                         :: XmlRegex+declSig                         = mkSeq (mkPrimA isDecl) (mkSeq (mkStar (mkPrimA isSig)) leadingDoc)++-- remove a leading class="doc" row this does not form a declaration+-- split the list of trees and throw away the first part++remLeadingDocElems              :: LA XmlTree XmlTree -> LA XmlTree XmlTree+remLeadingDocElems ts           = (splitRegexA leadingDoc ts >>^ snd) >>> unlistA++-- group the "tr" trees for a declaration together, build a "tr class="decl"" element and+-- throw the old "tr" s away++groupDeclSig                    :: LA XmlTree XmlTree -> LA XmlTree XmlTree+groupDeclSig ts                 = scanRegexA declSig ts +                                  >>>+                                  mkelem  "tr"+                                  [ sattr "class" "decl"+                                  , attr "id" (unlistA >>> getDeclName >>> mkText)+                                  ] +                                  [ mkelem "td" [sattr "class" "decl"] +                                                [unlistA+                                                 >>> getXPathTrees "//td[@class='decl' or @class='signature']"+                                                 >>> getChildren+                                                ] +                                  , mkelem "td" [sattr "class" "doc" ]+                                                [unlistA+                                                 >>> getXPathTrees "//td[@class='doc']"+                                                 >>> getChildren+                                                ] +                                  ]++-- ------------------------------------------------------------++getTitle                        :: LA XmlTree String+getTitle                        = xshow+                                  ( getPath "html/head/title"+                                    >>> deep isText+                                  )++getPackage                      :: LA XmlTree String+getPackage                      = getAttrValue transferURI+                                  >>^   +                                  hayooGetPackage++addPackageAttr                  :: LA XmlTree XmlTree+addPackageAttr                  = this += (attr "package" $ getPackage >>> mkText)++addModuleAttr                   :: LA XmlTree XmlTree+addModuleAttr                   = this += ( attr "module" $ getTitle   >>> mkText)++-- ------------------------------------------------------------++getPath                         :: String -> LA XmlTree XmlTree+getPath                         = foldl (/>) this . map hasName . split "/"++trtdWithClass                   :: (String -> Bool) -> LA XmlTree XmlTree+trtdWithClass av                = hasName "tr"+                                  />+                                  tdWithClass av++getClassPart                    :: String -> LA XmlTree XmlTree+getClassPart c                  = trtdWithClass (== "body")+                                  /> hasName "table"+                                  /> trtdWithClass (== c)++-- ------------------------------------------------------------++-- mother's little helpers++firstChild                      :: LA XmlTree XmlTree -> LA XmlTree XmlTree+firstChild sel                  = single (getChildren >>> sel)+++getById                         :: String -> LA XmlTree XmlTree+getById id'                     = single (deep (hasAttrValue "id" (== id')))++firstChildWithClass             :: String -> String -> LA XmlTree XmlTree+firstChildWithClass e c         = firstChild (isElemWithAttr e "class" (== c))++first_p_src                     :: LA XmlTree XmlTree+first_p_src                     = firstChildWithClass "p" "src"+ +divWithClass                    :: (String -> Bool) -> LA XmlTree XmlTree+divWithClass                    = isElemWithAttr "div" "class"++spanWithClass                   :: (String -> Bool) -> LA XmlTree XmlTree+spanWithClass                   = isElemWithAttr "span" "class"++pWithClass                      :: (String -> Bool) -> LA XmlTree XmlTree+pWithClass                      = isElemWithAttr "p" "class"++aWithClass                      :: (String -> Bool) -> LA XmlTree XmlTree+aWithClass                      = isElemWithAttr "a" "class"++trWithClass                     :: (String -> Bool) -> LA XmlTree XmlTree+trWithClass                     = isElemWithAttr "tr" "class"++tdWithClass                     :: (String -> Bool) -> LA XmlTree XmlTree+tdWithClass                     = isElemWithAttr "td" "class"++aWithHref                       :: (String -> Bool) -> LA XmlTree XmlTree+aWithHref                       = isElemWithAttr "a" "href"++-- ------------------------------------------------------------++mkDecl0                         :: String -> LA b XmlTree -> LA b XmlTree+mkDecl0 typ body                = ( eelem "div"+                                    += sattr "class" "top"+                                    += sattr "type"  typ+                                    += body+                                  )++mkDecl1                         :: String -> LA b XmlTree -> LA b XmlTree -> LA b XmlTree+mkDecl1 typ src doc             = mkDecl0 typ+                                  ( ( eelem "p"+                                      += sattr "class" "src"+                                      += src+                                    )+                                    <+>+                                    ( eelem "div"+                                      += sattr "class" "doc"+                                      += doc+                                    )+                                  )++mkFctDecl                       :: LA b XmlTree -> LA b XmlTree -> LA b XmlTree+mkFctDecl                       = mkDecl1 "function"++mkModuleDecl                    :: LA b XmlTree -> LA b XmlTree -> LA b XmlTree+mkModuleDecl                    = mkDecl1 "module"++-- ------------------------------------------------------------++{- tests for splitting function signatures for a list of functions++processFunctionSig          :: LA XmlTree XmlTree+processFunctionSig          = mkSingleFct $< splitMultiFct+    where+      mkSingleFct ts        = replaceChildren (constL ts)+      splitMultiFct         = listA getChildren+                              >>> spanA (neg $ hasText ((== "::") . stringTrim))+                              >>> first ( unlistA+                                          >>> aWithClass (== "def")+                                          >>> hasAttr "name"+                                        ) +                              >>> arr (uncurry (:))++processFunctionDiv         :: LA XmlTree XmlTree+processFunctionDiv         = (mkSingleDiv $< splitMultiDiv)+                              += sattr "type" "function"+    where+      mkSingleDiv ts        = replaceChildren (constL ts)+      splitMultiDiv         = listA getChildren+                              >>> arr (head &&& tail)+                              >>> first processFunctionSig+                              >>> arr (uncurry (:))++x3 = concat $+     [   "<p class=\"src\">"+     ,     "<a name=\"v:t_xml\" class=\"def\">t_xml</a>"+     ,     ", "+     ,     "<a name=\"v:t_root\" class=\"def\">t_root</a>"+     ,     " :: "+     ,     "<a href=\"/packages/archive/base/4.4.1.0/doc/html/Data-String.html#t:String\">String</a>"+     ,     "<a href=\"src/Text-XML-HXT-DOM-XmlKeywords.html#t_xml\" class=\"link\">Source</a>"+     ,   "</p>"+     ]++x4 = concat $+     [ "<div class=\"top\">"+     , x3+     ,   "<div class=\"doc\">"+     ,     "<p>the predefined namespace uri for xml: &quot;http://www.w3.org/XML/1998/namespace&quot;</p>"+     ,   "</div>"+     , "</div>"+     ]++x2 = "<a><c></c> :: <b></b>::</a>"++show3 = test this x3+show4 = test this x4++-- split a function def like "f1, f2 :: T"+-- in two independent declarations "f1 :: T" and "f2 :: T"+test3 = test processFunctionSig x3++-- split the whole div element containing the function signature+test4  = test processFunctionDiv x4++test f x = sequence $ map putStrLn $ runLA (xread >>> f >>> xshow indentDoc) $ x+++-- -}++-- ------------------------------------------------------------
+ src/Hayoo/IndexConfig.hs view
@@ -0,0 +1,198 @@+{-# OPTIONS #-}++-- ------------------------------------------------------------++module Hayoo.IndexConfig+where++import           Data.Char.Properties.XMLCharProps++import           Hayoo.Haddock+import           Hayoo.HackagePackage+import           Hayoo.Signature++import           Holumbus.Crawler+import           Holumbus.Crawler.IndexerCore++import           Text.Regex.XMLSchema.String      ( matchSubex )++import           Text.XML.HXT.Core++-- ------------------------------------------------------------++hayooIndexContextConfig         :: [IndexContextConfig]+hayooIndexContextConfig         = [ ixModule+                                  , ixHierachy+                                  , ixPackage+                                  , ixName+                                  , ixPartial+                                  , ixSignature+                                  , ixNormalizedSig+                                  , ixDescription+                                  ]+    where+    ixDefault                   = IndexContextConfig+                                  { ixc_name            = "default"+                                  , ixc_collectText     = getHtmlPlainText+                                  , ixc_textToWords     = deleteNotAllowedChars >>> words+                                  , ixc_boringWord      = boringWord+                                  }+    ixModule                    = ixDefault+                                  { ixc_name            = "module"+                                  , ixc_collectText     = getAttrValue "module"+                                  , ixc_textToWords     = return+                                  , ixc_boringWord      = null+                                  }+    ixHierachy                  = ixModule+                                  { ixc_name            = "hierarchy"+                                  , ixc_textToWords     = tokenize "[^.]+"              -- split module name at .+                                  }+    ixPackage                   = ixDefault+                                  { ixc_name            = "package"+                                  , ixc_collectText     = getAttrValue "package"+                                  , ixc_textToWords     = return+                                  , ixc_boringWord      = \ x -> null x || x == "unknownpackage"+                                  }+    ixName                      = ixDefault+                                  { ixc_name            = "name"+                                  , ixc_collectText     = getAttrValue "title"          -- is simpler than: fromLA $ getAllText (deep $ trtdWithClass (== "decl")) -- TODO 2.8 version+                                  , ixc_textToWords     = removePars                    --                  tokenize "[^ ():]+" >>> take 1+                                  , ixc_boringWord      = null                          -- (`elem` ["type", "class", "data", "module"])+                                  }+    ixPartial                   = ixName+                                  { ixc_name            = "partial"+                                  , ixc_textToWords     = deCamel >>> tokenize typeIdent+                                  , ixc_boringWord      = boringWord+                                  }+    ixSignature                 = ixDefault+                                  { ixc_name            = "signature"+                                  , ixc_collectText     = getAttrValue "signature"+                                  , ixc_textToWords     = stripSignature >>> return+                                  , ixc_boringWord      = not . isSignature+                                  }+    ixNormalizedSig             = ixSignature+                                  { ixc_name            = "normalized"+                                  , ixc_textToWords     = normalizeSignature >>> return+                                  }+    ixDescription               = ixDefault+                                  { ixc_name            = "description"+                                  , ixc_collectText     = fromLA $ getAllText hayooGetDescr+                                  , ixc_textToWords     = tokenize descrWord+                                  }++-- -----------------------------------------------------------------------------    ++hayooPkgIndexContextConfig      :: [IndexContextConfig]+hayooPkgIndexContextConfig      = [ ixCategory+                                  , ixPkgName+                                  , ixDepends+                                  , ixDescription+                                  , ixSynopsis+                                  , ixAuthor+                                  ]+    where+    ixDefault                   = IndexContextConfig+                                  { ixc_name            = "default"+                                  , ixc_collectText     = getHtmlPlainText+                                  , ixc_textToWords     = deleteNotAllowedChars >>> words+                                  , ixc_boringWord      = boringWord+                                  }+    ixCategory                  = ixDefault+                                  { ixc_name            = "category"+                                  , ixc_collectText     = fromLA getPkgCategory+                                  , ixc_textToWords     = words+                                  }+    ixPkgName                   = ixDefault+                                  { ixc_name            = "pkgname"+                                  , ixc_collectText     = fromLA $ getPkgName+                                  , ixc_textToWords     = splitDash+                                  }+    ixDepends                   = ixDefault+                                  { ixc_name            = "dependencies"+                                  , ixc_collectText     = fromLA $ getPkgDependencies >>^ unwords+                                  , ixc_textToWords     = words+                                  }+    ixDescription               = ixDefault+                                  { ixc_name            = "pkgdescr"+                                  , ixc_collectText     = fromLA $ getPkgDescr+                                  , ixc_textToWords     = tokenize descrWord+                                  }+    ixSynopsis                  = ixDescription+                                  { ixc_name            = "synopsis"+                                  , ixc_collectText     = fromLA $ getPkgSynopsis+                                  }+    ixAuthor                    = ixDescription+                                  { ixc_name            = "author"+                                  , ixc_collectText     = fromLA $+                                                          listA (getPkgAuthor <+> getPkgMaintainer) >>^ unwords+                                  }++-- -----------------------------------------------------------------------------    ++-- please: "-" as 1. char in set !!!+-- words start with a letter, end with a letter or digit and may contain -, . and @ and digits++descrWord                       :: String+descrWord                       = "[A-Za-z][-A-Za-z0-9.@]*[A-Za-z0-9]"++-- -----------------------------------------------------------------------------    ++deCamel                         :: String -> String+deCamel                         = deCamel' False+    where+    deCamel' _ []               = []+    deCamel' _ ('_' : xs)       = ' ' : deCamel' True xs+    deCamel' cap (x : xs)+        | isCap x               = ( if cap+                                    then id+                                    else (' ' :)+                                  ) $+                                  x : deCamel' True      xs+        | otherwise             = x : deCamel' (isCap x) xs+        where+        isCap                   = (`elem` ['A'..'Z'])++-- -----------------------------------------------------------------------------    ++boringWord                      :: String -> Bool+boringWord w                    = null w+                                  ||+                                  (null . tail $ w)+                                  ||+                                  not (any isXmlLetter w)++isAllowedWordChar               :: Char -> Bool+isAllowedWordChar c             = isXmlLetter c+                                  ||+                                  isXmlDigit c+                                  ||+                                  c `elem` "_-"++deleteNotAllowedChars           :: String -> String+deleteNotAllowedChars           = map notAllowedToSpace+    where+    notAllowedToSpace c+        | isAllowedWordChar c   = c+        | otherwise             = ' '++splitDash                       :: String -> [String]+splitDash s                     = ( s : (map (\ x -> if x == '-' then ' ' else x)+                                         >>>+                                         words+                                         >>>+                                         drop 1+                                         $+                                          s+                                        )+                                  )++matchPars                       :: String -> [(String, String)]+matchPars                       = matchSubex "[(]({m}.+)[)]"++removePars                      :: String -> [String]+removePars xs+    = case matchPars xs of+        [("m", xs')]            -> [xs, xs']+        _                       -> [xs]++-- -----------------------------------------------------------------------------    
+ src/Hayoo/IndexTypes.hs view
@@ -0,0 +1,163 @@+{-# OPTIONS #-}++-- ------------------------------------------------------------++module Hayoo.IndexTypes+    ( module Hayoo.IndexTypes+    , module Holumbus.Index.CompactIndex+    , FunctionInfo(..)+    , PackageInfo(..)+    , Score+    )+where++import           Control.Arrow+import           Control.DeepSeq++import           Data.Binary++import           Data.List                      ( foldl' )+import qualified Data.Map                       as M    ( lookup )+import           Data.Maybe++import           Hayoo.FunctionInfo+import           Hayoo.PackageInfo+import           Hayoo.PackageRank++import           Holumbus.Crawler+import           Holumbus.Crawler.IndexerCore+import qualified Holumbus.Data.PrefixTree       as PT+import           Holumbus.Index.Common          ( Document(..)+                                                , custom+                                                , elemsDocIdMap+                                                , emptyDocIdMap+                                                , emptyPos+                                                , foldWithKeyDocIdMap+                                                , insertDocIdMap+                                                , keysDocIdMap+                                                , removeById+                                                , toMap+                                                , updateDocuments+                                                )+import           Holumbus.Index.CompactIndex+import           Holumbus.Query.Result          ( Score )++-- ------------------------------------------------------------++getPkgNameFct                   :: Document FunctionInfo -> String+getPkgNameFct                   = package . fromJust . custom++getPkgNamePkg                   :: Document PackageInfo -> String+getPkgNamePkg                   = p_name . fromJust . custom++-- ------------------------------------------------------------++removePackages'                 :: (Binary di, NFData di) =>+                                   (Document di -> String) -> String ->+                                   [String] -> Bool -> IO (HolumbusState di)+removePackages' pkgName ixName pkgList defragment+                                = do+                                  ix <- decodeFile ixName+                                  let ix1  = removePack' pkgName pkgList ix+                                  let ix2  = if defragment+                                             then defragmentHolumbusState ix1+                                             else ix1+                                  rnf ix2 `seq`+                                      return ix2++-- ------------------------------------------------------------++removePack'                     :: (Binary di) =>+                                   (Document di -> String) -> [String] ->+                                   HolumbusState di -> HolumbusState di+removePack' pkgName ps IndexerState+              { ixs_index     = ix+              , ixs_documents = ds+              }                 = IndexerState+                                  { ixs_index     = ix'+                                  , ixs_documents = ds'+                                  }+    where+                                                        -- collect all DocIds used in the given packages+    docIds                      = foldWithKeyDocIdMap checkDoc emptyDocIdMap . toMap $ ds+    checkDoc did doc xs+        | docPartOfPack         = insertDocIdMap did emptyPos xs+        | otherwise             =                             xs+        where+        docPartOfPack           = (`elem` ps) . pkgName $ doc++                                                        -- remove all DocIds from index+    ix'                         = removeDocIdsInverted docIds ix++                                                        -- restrict document table+    ds'                         = foldl' removeById ds $ keysDocIdMap docIds++-- ------------------------------------------------------------++-- | package ranking is implemented by the following algorithm+--+-- .1 the rank of a package not used by another package is 1.0+--+-- .2 the rank of a package used by other packages is 1.0 + 0.5 * sum of the ranks of the+--    directly dependent packages. Example: a depends on b, b depends on c, d depends on c:+--    rank a = 1.0, rank b = 1.5, rank c = 2.25, rank d = 1.0+--+-- .3 this leads to a ranking where rank base > 1000.0 and rank bytestring > 300. To+--    reduce the weight differences, the log to base 2 is taken instead of the direct value++packageRanking                  :: HayooPkgIndexerState -> HayooPkgIndexerState+packageRanking ixs@(IndexerState { ixs_documents = ds })+                                = ixs { ixs_documents = updateDocuments insertRank ds }+    where+    deflate                     = 0.5+    scale                       = (/10.0) . fromInteger . round . (*10) . (+1.0) . logBase 2+    rank                        = ranking deflate+                                  . dagFromList+                                  . map (\ p -> (getPackageName p, getPackageDependencies p))+                                  . map fromJust+                                  . filter isJust               -- all illegal package refs are filtered out (there are illegal refs)+                                  . map custom+                                  . elemsDocIdMap+                                  . toMap $ ds++    insertRank d                = d { custom = fmap insertRank' (custom d) }+        where+        insertRank' ci          = setPackageRank (scale . fromMaybe (1.0) . M.lookup (getPackageName ci) $ rank) ci++{-+traceNothing d+    | isJust . custom $ d       = d+    | otherwise                 = traceShow d $ d+-- -}++-- ------------------------------------------------------------++type RankTable                  = PT.PrefixTree Score++lookupRankTable                 :: String -> RankTable -> Score+lookupRankTable p               = fromMaybe 1.0 . PT.lookup p++buildRankTable                  :: SmallDocuments PackageInfo -> RankTable+buildRankTable                  = toMap+                                  >>> elemsDocIdMap+                                  >>> map ( custom+                                            >>> fromJust+                                            >>> (p_name &&& p_rank)+                                          )+                                  >>> PT.fromList ++-- ------------------------------------------------------------++type HayooIndexerState                  = HolumbusState   FunctionInfo+type HayooIndexerConfig                 = HolumbusConfig  FunctionInfo++type HayooIndexerCrawlerState           = CrawlerState HayooIndexerState++-- ------------------------------------------------------------++type HayooPkgIndexerState               = HolumbusState   PackageInfo+type HayooPkgIndexerConfig              = HolumbusConfig  PackageInfo++type HayooPkgIndexerCrawlerState        = CrawlerState HayooPkgIndexerState++-- ------------------------------------------------------------
+ src/Hayoo/PackageArchive.hs view
@@ -0,0 +1,74 @@+module Hayoo.PackageArchive+where+++import qualified Codec.Archive.Tar              as Tar+import qualified Codec.Archive.Tar.Entry        as Tar++import qualified Codec.Compression.GZip         as GZip ( decompress )++import           Control.Arrow++import           Data.ByteString.Lazy           ( ByteString )+import qualified Data.ByteString.Lazy           as BS+import           Data.List++import           System.FilePath+import           System.Process                 ( system )+import           System.Time++-- ------------------------------------------------------------++getNewPackages          :: Bool -> Int -> IO [String]+getNewPackages updateArchive since+                        = do+                          if updateArchive+                             -- this is a hack, should be done more elegant without the use of system and wget+                             then system ("wget http://hackage.haskell.org/packages/" ++ archiveFile ++ " -O cache/" ++ archiveFile)+                                  >> return ()+                             else return ()+                          t <- secondsAgo+                          a <- BS.readFile $ "cache/" ++ archiveFile+                          return $ latestPackages t a+    where+    archiveFile         = "00-index.tar.gz"+    secondsAgo          :: IO ClockTime+    secondsAgo          = do+                          (TOD s _f) <- getClockTime+                          return $ TOD (s - toInteger since) 0++-- ------------------------------------------------------------++latestPackages          :: ClockTime -> ByteString -> [String]+latestPackages since    =  nub . sort . map fst . filterPackages since . selectPackages++filterPackages          :: ClockTime -> [(String, (String, ClockTime))] -> [(String, (String, ClockTime))]+filterPackages since    = filter ((since <=) . snd . snd)++selectPackages          :: ByteString -> [(String, (String, ClockTime))]+selectPackages          = GZip.decompress+                          >>> Tar.read+                          >>> Tar.foldEntries (\ e -> (entryInfo e ++)) [] (const [])++entryInfo               :: Tar.Entry -> [(String, (String, ClockTime))]+entryInfo e+    | isFile e+      &&+      isCabal path      = [(package, (version, time))]+    | otherwise         = []+    where+    path                = Tar.entryPath e+    (version : package : _)+                        = splitDirectories >>> reverse >>> tail $ path+    time                = Tar.entryTime >>> epochTimeToClockTime $ e+    isCabal             = takeExtension >>> (== ".cabal")+    isFile e'           = case Tar.entryContent e' of+                          Tar.NormalFile _ _    -> True+                          _                     -> False++epochTimeToClockTime :: Tar.EpochTime -> ClockTime+epochTimeToClockTime e = TOD s (truncate (1000000000 * f))+    where (s,f) = properFraction (toRational e)+++-- ------------------------------------------------------------
+ src/Hayoo/PackageInfo.hs view
@@ -0,0 +1,104 @@+{-# OPTIONS #-}++-- ------------------------------------------------------------++module Hayoo.PackageInfo+where++import           Control.DeepSeq++import           Data.Binary                    ( Binary(..) )+import qualified Data.Binary                    as B++import           Holumbus.Query.Result          ( Score )++import           Text.XML.HXT.Core++-- ------------------------------------------------------------++-- | Additional information about a function.+--+-- The strings in this record are not compressed into bytestrings.+-- The document table contains bzip compressed bytestings of serialized+-- records, which are unpacked when accessing the document descriptions.+-- So there is non need for unsing bytestrings and strict fields++data PackageInfo+    = PackageInfo +      { p_name              :: String               -- ^ The name of the package+      , p_version           :: String               -- ^ The latest package version+      , p_dependencies      :: String               -- ^ The list of required packages+      , p_author            :: String               -- ^ The author+      , p_maintainer        :: String               -- ^ The maintainer+      , p_category          :: String               -- ^ The package category+      , p_homepage          :: String               -- ^ The home page+      , p_synopsis          :: String               -- ^ The synopsis+      , p_description       :: String               -- ^ The description of the package+      , p_rank              :: Score                -- ^ The ranking+      } +    deriving (Show, Eq)++mkPackageInfo                   :: String -> String -> [String] -> String -> String -> String -> String -> String -> String -> PackageInfo+mkPackageInfo n v d a m c h y s = PackageInfo n v (unwords d) a m c h y s 1.0++setPackageRank                  :: Score -> PackageInfo -> PackageInfo+setPackageRank r p              = r `seq` p { p_rank = r }++getPackageName                  :: PackageInfo -> String+getPackageName                  =  p_name++getPackageDependencies          :: PackageInfo -> [String]+getPackageDependencies          = words . p_dependencies++instance XmlPickler PackageInfo where+    xpickle                     = xpWrap (fromTuple, toTuple) xpPackage+        where+        fromTuple ((n, v, d), a, m, c, h, (y, s, r))+                                = PackageInfo n v d a m c h y s r+        toTuple (PackageInfo n v d a m c h y s r)+                                = ((n, v, d)+                                  , a, m, c, h+                                  ,(y, s, r)+                                  )+        xpPackage               = xp6Tuple+                                  (xpTriple xpName xpVersion xpDependencies)+                                  xpAuthor xpMaintainer xpCategory xpHomepage+                                  (xpTriple xpSynopsis xpDescr xpRank)+            where                               +            xpName              = xpAttr "name"         xpText0+            xpVersion           = xpAttr "version"      xpText0+            xpDependencies      = xpAttr "dependencies" xpText0+            xpAuthor            = xpAttr "author"       xpText0+            xpMaintainer        = xpAttr "maintainer"   xpText0+            xpCategory          = xpAttr "category"     xpText0+            xpHomepage          = xpAttr "homepage"     xpText0+            xpSynopsis          = xpAttr "synopsis"     xpText0+            xpDescr             = xpText+            xpRank              = xpAttr "rank" $+                                  xpWrap (read, show)   xpText0++instance NFData PackageInfo where+  rnf (PackageInfo n v d a m c h y s r)+                                = rnf n `seq` rnf v `seq` rnf d `seq` rnf a `seq`+                                  rnf m `seq` rnf c `seq` rnf h `seq` rnf y `seq`+                                  rnf s `seq` r          `seq` ()++instance B.Binary PackageInfo where+    put (PackageInfo x1 x2 x3 x4 x5 x6 x7 x8 x9 x10)+                                = put x1 >> put x2 >> put x3 >> put x4 >> put x5 >>+                                  put x6 >> put x7 >> put x8 >> put x9 >> put x10+    get                         = do+                                  x1 <- get+                                  x2 <- get+                                  x3 <- get+                                  x4 <- get+                                  x5 <- get+                                  x6 <- get+                                  x7 <- get+                                  x8 <- get+                                  x9 <- get+                                  x10 <- get+                                  let r = PackageInfo x1 x2 x3 x4 x5 x6 x7 x8 x9 x10+                                  rnf r `seq` return r++-- ------------------------------------------------------------
+ src/Hayoo/PackageRank.hs view
@@ -0,0 +1,126 @@+{-# OPTIONS #-}++-- ------------------------------------------------------------++module Hayoo.PackageRank+where++import           Control.Arrow++import           Data.Map       ( Map )+import qualified Data.Map       as M+import           Data.Maybe+import           Data.Set       ( Set )+import qualified Data.Set       as S+{-+import           Debug.Trace+-- -}+-- ------------------------------------------------------------++type DAG a                      = Map a (Set a)++type Ranking a                  = Map a Double++-- ------------------------------------------------------------++-- | construct a directed graph form a list of nodes and successors.+--+--  This version does not check cycles, so it only works savely,+--  when this is checked before.++unsaveDagFromList               :: (Ord a, Show a) => [(a, [a])] -> DAG a+unsaveDagFromList l             = -- traceShow l $+                                  map (second S.fromList) >>> M.fromList $ l++-- | Construct a directed graph (DAG) form a list of nodes and successors.+--+-- The function checks for edges, that would introduce cycles, and deletes these+-- edges. So if there are cycles in the input list, the result depends on the+-- sequence of the pairs in the input list++dagFromList                     :: (Ord a, Show a) => [(a, [a])] -> DAG a+dagFromList l                   = -- traceShow l $+                                  map (second S.fromList)+                                  >>>+                                  foldl (flip insEdges) M.empty $ l++insEdges                        :: (Ord a, Show a) => (a, Set a) -> DAG a -> DAG a+insEdges (x, ys) g              = S.fold insEdge' g $ ys+    where+    insEdge' y' g'              = insertEdge x y' g'++-- ------------------------------------------------------------++-- | insert an edge from x to y into DAG g.+--+-- Check for possible cycles. Edges leading to cycles are discarded++insertEdge                      :: (Ord a, Show a) => a -> a -> DAG a -> DAG a+insertEdge x y g+    | x == y                    = -- traceShow ("cycle:", [x,y]) $+                                  g+    | existPath                 = -- traceShow ("cycle:", x:(head path)) $+                                  g+    | otherwise                 = M.insertWith S.union x (S.singleton y) g+    where+    path                        = take 1 $ allPaths g y x+    existPath                   = not . null $ path++-- ------------------------------------------------------------++-- | Compute all paths from one node to another.+--+-- this is used by insertEdge, when checking for cycles++allPaths                        :: (Ord a) => DAG a -> a -> a -> [[a]]+allPaths g                      = allPaths'+    where+    allPaths' x y+        | y `S.member` succs    = [[x,y]]+        | otherwise             = map (x:) . concatMap (flip allPaths' y) . S.toList $ succs+        where+        succs                   = fromMaybe S.empty . M.lookup x $ g++-- ------------------------------------------------------------++-- | Inverse to dagFromList++dagToList                       :: DAG a -> [(a, [a])]+dagToList                       = M.toList >>> map (second S.toList)++-- ------------------------------------------------------------++-- | Switch the direction in the DAG++dagInvert                       :: (Ord a) => DAG a -> DAG a+dagInvert                       = M.foldrWithKey invVs M.empty+    where+    invVs k ks acc              = S.fold invV acc1 $ ks+        where+        acc1                    = M.insertWith S.union k  S.empty         $ acc         -- don't forget the roots+        invV k' acc'            = M.insertWith S.union k' (S.singleton k) $ acc'++-- ------------------------------------------------------------++ranking                         :: (Ord a, Show a) => Double -> DAG a -> Ranking a+ranking w g                     = -- traceShow r+                                  r+    where+    g'                          = dagInvert g+    r                           = foldl insertRank M.empty $ M.keys g+        where+        insertRank r' k         = M.insert k (w * (S.fold accRank (1/w) usedBy)) r'+            where+            usedBy              = fromMaybe S.empty . M.lookup k $ g'+            accRank k' acc'     = ( fromJust . M.lookup k' $ r ) + acc'++-- ------------------------------------------------------------+{- minimal test case++d1 :: DAG Int+d1 = dagFromList [(1,[2,3])+                 ,(2,[3,4])+                 ,(3,[]),(4,[])+                 ]+-- -}+-- ------------------------------------------------------------
+ src/Hayoo/Search/Application.hs view
@@ -0,0 +1,172 @@+-- ----------------------------------------------------------------------------++{- |+  Module     : Hayoo.SearchApplication+  Copyright  : Copyright (C) 2010 Timo B. Huebel+  License    : MIT++  Maintainer : Timo B. Huebel (tbh@holumbus.org)+  Stability  : experimental+  Portability: portable+  Version    : 0.1++  The search web-service for the Hayoo Haskell API search engine.++-}++-- ----------------------------------------------------------------------------++module Hayoo.Search.Application+    ( hayooApplication+    , hayooInit+    , Core (..)+    )+where++import Control.Concurrent               -- For the global MVar++import Data.ByteString.Lazy.Char8       ( fromChunks )++import qualified Data.List              as L+import qualified Data.Text.Encoding     as T++import Hayoo.IndexTypes+import Hayoo.Search.EvalSearch+import Hayoo.Search.Pages.Template+import Hayoo.Search.Pages.Static++import Holumbus.Index.Common++import Hack+import Hack.Contrib.Middleware.File+import Hack.Contrib.Middleware.URLMap+import Hack.Contrib.Request             ( params, path, host )+import Hack.Contrib.Utils               ( empty_app )++import System.IO                        ( stdout )+import System.Time++import System.Log.Logger+import System.Log.Handler.Simple++import qualified+       Text.XHtmlCombinators            as X++-- ------------------------------------------------------------++-- | Init Hayoo!+hayooInit :: FilePath -> IO Application+hayooInit ixBase = do+    fdl  <- fileHandler "hayoo.log" INFO+    sdl  <- streamHandler stdout INFO++    updateGlobalLogger rootLoggerName (setHandlers [fdl, sdl])+    updateGlobalLogger rootLoggerName (setLevel INFO)++    idx  <- loadIndex     hayooIndex+    infoM "Hayoo.Main" ("Hayoo index   loaded from file " ++ show hayooIndex)++    doc  <- loadDocuments hayooDocs+    infoM "Hayoo.Main" ("Hayoo docs    loaded from file " ++ show hayooDocs )+    infoM "Hayoo.Main" ("Hayoo docs contains " ++ show (sizeDocs doc) ++ " functions and types")++    pidx <- loadIndex     hackageIndex+    infoM "Hayoo.Main" ("Hackage index loaded from file " ++ show hackageIndex)++    pdoc <- loadPkgDocs   hackageDocs+    infoM "Hayoo.Main" ("Hackage docs  loaded from file " ++ show hackageDocs)+    infoM "Hayoo.Main" ("Hackage docs contains " ++ show (sizeDocs pdoc) ++ " packages")++    prnk <- return $ buildRankTable pdoc+    infoM "Hayoo.Main" ("Hackage package rank table computed")++    tpl  <- return $ makeTemplate (sizeDocs pdoc) (sizeDocs doc)++    midct <- newMVar $+             Core+             { index      = idx+             , documents  = doc+             , pkgIndex   = pidx+             , pkgDocs    = pdoc+             , template   = tpl+             , packRank   = prnk+             }++    return $+           url_map [ ("/hayoo.html", hayooApplication midct)+                   , ("/hayoo.json", hayooApplication midct)+                   , ("/help.html", serveStatic $ tpl help)+                   , ("/about.html", serveStatic $ tpl about)+                   , ("/api.html", serveStatic $ tpl api)+                   ] (file Nothing empty_app)+  where+  hayooIndex      = ixBase ++ "/ix.bin.idx"+  hayooDocs       = ixBase ++ "/ix.bin.doc"+  hackageIndex    = ixBase ++ "/pkg.bin.idx"+  hackageDocs     = ixBase ++ "/pkg.bin.doc"+  serveStatic c _ = return $ Response +                    { status = 200+                    , headers = [ ("Content-Type", "text/html") ]+                    , body = fromChunks [T.encodeUtf8 $ X.render c]+                    }++-- | Generate the actual response+hayooApplication :: MVar Core -> Env -> IO Response+hayooApplication midct env      = let p = params env in do+          request <- return $               getValDef p "query"  ""+          start   <- return $ readDef 0    (getValDef p "start"  "")+          static  <- return $ readDef True (getValDef p "static" "")+          json    <- return $ isJson (path env)++          -- Output some information about the request.+          logRequest env++          -- Because index access is read only, the MVar's are just read +          -- to make them avaliable again.+          idct    <- readMVar midct++          -- Put all information relevant for rendering into a container+          state   <- return $ (request, start, static, (template idct))++          -- If the query is empty, just render an empty page+          resp    <- return $+                     if L.null request+                     then renderEmpty json idct+                     else renderResult state json idct++          -- Determine the mime type of the response+          mime    <- return $ +                     if json +                     then "application/json" +                     else "text/html"++          -- Return the actual response+          return $ Response { status = 200, headers = [ ("Content-Type", mime) ], body = resp }+++-- | Log a request to stdout.+logRequest :: Env -> IO ()+logRequest env = do+  -- Extract remote host and the search string from the incoming transaction.+  remHost    <- return $ host env+  rawRequest <- return $ getValDef (params env) "query" ""+  start      <- return $ getValDef (params env) "start" "0"+  userAgent  <- return $ getValDef (http env) "User-Agent" "No user agent"++  -- Decode URI encoded entities in the search string.+  decodedRequest <- return $ decode rawRequest++  -- Get the current date and time.+  unixTime <- getClockTime+  currTime <- return $ calendarTimeToString $ toUTCTime unixTime++  -- Output all the collected information from above to stdout.+  infoM "Hayoo.Request" (currTime ++ "\t" +++                         remHost ++ "\t "+++                         userAgent ++ "\t" +++                         rawRequest ++ "\t" +++                         decodedRequest ++ "\t" +++                         start+                        )++-- ------------------------------------------------------------
+ src/Hayoo/Search/Common.hs view
@@ -0,0 +1,38 @@+-- ----------------------------------------------------------------------------++{- |+  Module     : Hayoo.Search.Common+  Copyright  : Copyright (C) 2008 Timo B. Huebel+  License    : MIT++  Maintainer : Timo B. Huebel (tbh@holumbus.org)+  Stability  : experimental+  Portability: portable+  Version    : 0.1++  Helper functions and types used by the Hayoo crawler and the+  Hayoo web search.++-}++-- ----------------------------------------------------------------------------++module Hayoo.Search.Common where++import Holumbus.Query.Result    ( Result )++import Hayoo.IndexTypes         ( FunctionInfo, PackageInfo )++-- ----------------------------------------------------------------------------++-- Status information of query processing (status message, result, top modules, top packages).++type StatusResult+    = ( String+      , Result FunctionInfo+      , Result PackageInfo+      , [(String, Int)]+      , [(String, Int)]+      )++-- ----------------------------------------------------------------------------
+ src/Hayoo/Search/EvalSearch.hs view
@@ -0,0 +1,334 @@+-- ----------------------------------------------------------------------------++{- |+  Module     : Hayoo.SearchApplication+  Copyright  : Copyright (C) 2010 Timo B. Huebel+  License    : MIT++  Maintainer : Timo B. Huebel (tbh@holumbus.org)+  Stability  : experimental+  Portability: portable+  Version    : 0.1++  The search web-service for the Hayoo Haskell API search engine.++-}++-- ----------------------------------------------------------------------------++module Hayoo.Search.EvalSearch+    ( Core(..)++    , parseQuery+    , genResult+    , emptyRes++    , renderEmpty+    , renderResult++    , isJson+    , renderJson+    , renderEmptyJson++    , examples+    , filterStatusResult++    , decode++    , readDef+    , getValDef++    , loadIndex+    , loadDocuments+    , loadPkgDocs+    )+where++import Data.ByteString.Lazy.Char8       ( ByteString+                                        , pack+                                        , fromChunks+                                        )+import Data.Function+import Data.Maybe++import qualified Data.List              as L+import qualified Data.Map               as M+import qualified Data.Text.Encoding     as T++import Data.String.Unicode++import Holumbus.Index.Common++import Holumbus.Query.Language.Grammar+import Holumbus.Query.Processor+import Holumbus.Query.Result+import Holumbus.Query.Ranking+import Holumbus.Query.Fuzzy++import Holumbus.Utility++import Hayoo.IndexTypes+import Hayoo.Signature++import Hayoo.Search.Common+import Hayoo.Search.JSON+import Hayoo.Search.HTML+import Hayoo.Search.Parser++import Hayoo.Search.Pages.Template+import Hayoo.Search.Pages.Static++import Network.URI                      ( unEscapeString )++import System.FilePath                  ( takeExtension )++import qualified+       Text.XHtmlCombinators            as X++import Text.XML.HXT.Core++-- ------------------------------------------------------------++data Core = Core+          { index     :: !  CompactInverted+          , documents :: ! (SmallDocuments FunctionInfo)+          , pkgIndex  :: !  CompactInverted+          , pkgDocs   :: ! (SmallDocuments PackageInfo)+          , template  :: !  Template+          , packRank  :: !  RankTable+          }++-- ------------------------------------------------------------++-- | Weights for context weighted ranking.+contextWeights          :: [(Context, Score)]+contextWeights          = [ ("name",        0.9)+                          , ("partial",     0.8)+                          , ("module",      0.7)+                          , ("hierarchy",   0.6)+                          , ("package",     0.5)+                          , ("signature",   0.4)+                          , ("description", 0.2)+                          , ("normalized",  0.1)+                          ]++-- ------------------------------------------------------------++-- | Just an alias with explicit type.+loadIndex       :: FilePath -> IO CompactInverted+loadIndex       = loadFromFile++-- | Just an alias with explicit type.+loadDocuments   :: FilePath -> IO (SmallDocuments FunctionInfo)+loadDocuments   = loadFromFile++-- | Just an alias with explicit type.+loadPkgDocs     :: FilePath -> IO (SmallDocuments PackageInfo)+loadPkgDocs     = loadFromFile++-- ------------------------------------------------------------++-- Read or use default value+readDef                         :: Read a => a -> String -> a+readDef d                       = fromMaybe d . readM++getValDef                       :: [(String,String)] -> String -> String -> String+getValDef l k d                 = fromMaybe d (lookup k l)++-- | Enable handling of parse errors from 'read'.+readM                           :: (Read a, Monad m) => String -> m a+readM s                         = case reads s of+                                  [(x, "")] -> return x+                                  _         -> fail "No parse"++-- ------------------------------------------------------------++-- | Decode any URI encoded entities and transform to unicode.+decode                          :: String -> String+decode                          = fst . utf8ToUnicode . unEscapeString   -- with urlDecode the + disapears++-- | Perform some postprocessing on the status and the result.+filterStatusResult              :: String -> StatusResult -> StatusResult+filterStatusResult q (s, r@(Result dh wh), h, m, p)+                                = (s, filteredResult, h, m, p)+  where+  filteredResult+      | isSignature q           = r+      | otherwise               = Result dh (M.filterWithKey (\x _y -> not . isSignature $ x) wh)++-- | Just render an empty page/JSON answer++renderEmpty                     :: Bool -> Core -> ByteString+renderEmpty j idct+    | j                         = writeJson+    | otherwise                 = writeHtml+    where+    writeJson                   = pack $ renderEmptyJson+    writeHtml                   = fromChunks [T.encodeUtf8 $ X.render $ (template idct) examples]++-- | Parse the query and generate a result or an error depending on the parse result.++renderResult :: (String, Int, Bool, Template) -> Bool -> Core  -> ByteString+renderResult (r, s, i, t) j idct+                        = decode+                          >>>+                          parseQuery+                          >>>+                          either emptyRes (genResult idct)+                          >>>+                          ( if j+                            then pack . renderJson+                            else writeHtml (RenderState r s i)+                          )+                          $ r+      where+      writeHtml rs      = filterStatusResult r+                          >>>+                          arr (applyTemplate rs)+      applyTemplate rs sr+                        = fromChunks [T.encodeUtf8 markup]+          where+          markup        = let rr = result rs sr in +                          if rsStatic rs+                          then X.render $ t rr+                          else X.render $ rr++-- Check requested path for JSON+isJson                  :: FilePath -> Bool+isJson f                = takeExtension f == ".json"++-- ------------------------------------------------------------++hayooPkgRanking         :: RankTable -> DocId -> DocInfo PackageInfo -> DocContextHits -> Score+hayooPkgRanking rt _ di _+                        = maybe 1.0 (flip lookupRankTable rt . p_name) (custom $ document di)++-- | Customized Hayoo! ranking function for functions. Preferres exact matches and matches in Prelude and base.+hayooFctRanking                 :: RankTable -> [(Context, Score)] -> [String] -> DocId -> DocInfo FunctionInfo -> DocContextHits -> Score+hayooFctRanking rt ws ts _ di dch+                        = baseScore+                          * factModule+                          * factPackage+                          * factPrelude+                          * factExactMatch+  where+  fctInfo               = custom $ document di++  baseScore             = M.foldrWithKey calcWeightedScore 0.0 dch++  factExactMatch        = L.foldl' (\r t -> t == (title $ document di) || r) False+                          >>> fromEnum+                          >>> (+ 1)+                          >>> fromIntegral+                          >>> (* 4.0)+                          $ ts++  factPrelude           = fmap ( moduleName+                                 >>> (== "Prelude")+                                 >>> fromEnum+                                 >>> (+ 1)+                                 >>> fromIntegral+                                 >>> (* 2.0)+                               )+                          >>> fromMaybe 1.0+                          $ fctInfo++  factPackage           = fmap ( package+                                 >>> flip lookupRankTable rt+                               )+                          >>> fromMaybe 1.0+                          $ fctInfo++  factModule            = fmap ( moduleName+                                 >>> split "."+                                 >>> length+                                 >>> fromIntegral+                                 >>> (1.0 /)+                               )+                          >>> fromMaybe 1.0+                          $ fctInfo++  calcWeightedScore     :: Context -> DocWordHits -> Score -> Score+  calcWeightedScore c h r+                        = maybe r (\w -> r + ((w / mw) * count)) (lookupWeight ws)+    where+    count               = fromIntegral $ M.fold ((+) . sizePos) 0 h+    mw                  = snd $ L.maximumBy (compare `on` snd) ws+    lookupWeight []     = Nothing+    lookupWeight (x:xs) = if fst x == c then+                            if snd x /= 0.0+                            then Just (snd x)+                            else Nothing+                          else lookupWeight xs++emptyRes                :: String -> StatusResult+emptyRes msg            = ( tail . dropWhile ((/=) ':') $ msg+                          , emptyResult+                          , emptyResult+                          , []+                          , []+                          )++genResult ::  Core -> Query -> StatusResult+genResult idc q+      = let (fctRes, pkgRes) = curry makeQuery q idc in+        let (fctCfg, pkgCfg) = ( RankConfig (hayooFctRanking (packRank idc) contextWeights (extractTerms q)) wordRankByCount+                               , RankConfig (hayooPkgRanking (packRank idc))                                 wordRankByCount+                               ) in+        let (fctRnk, pkgRnk) = ( rank fctCfg fctRes+                               , rank pkgCfg pkgRes+                               ) in+        ( msgSuccess fctRnk pkgRnk+        , fctRnk+        , pkgRnk+        , genModules fctRnk+        , genPackages fctRnk+        ) -- Include a success message in the status++-- | Generate a success status response from a query result.+msgSuccess              :: Result FunctionInfo -> Result PackageInfo -> String+msgSuccess fr pr        = if sd + sp == 0+                          then "Nothing found yet."+                          else "Found " ++ (show sd) ++ " " ++ ds ++ ", " ++ (show sp) ++ " " ++ ps ++ " and " ++ (show sw) ++ " " ++ cs ++ "."+    where+    sd                  = sizeDocHits fr+    sp                  = sizeDocHits pr+    sw                  = sizeWordHits fr + sizeWordHits pr+    ds                  = if sd == 1 then "function" else "functions"+    ps                  = if sp == 1 then "package" else "packages"+    cs                  = if sw == 1 then "completion" else "completions"++-- | This is where the magic happens! This helper function really calls the+-- processing function which executes the query.+makeQuery               :: (Query, Core) -> (Result FunctionInfo, Result PackageInfo)+makeQuery (q, c)        = (processQuery cfg (index c) (documents c) q, processQuery cfg (pkgIndex c) (pkgDocs c) q)+    where+    cfg                 = ProcessConfig+                          { fuzzyConfig   = FuzzyConfig False True 1.0 []+                          , optimizeQuery = True+                          , wordLimit     = 50+                          , docLimit      = 500+                          }++-- | Generate a list of modules from a result+genModules              :: Result FunctionInfo -> [(String, Int)]+genModules r            = reverse $+                          L.sortBy (compare `on` snd) $+                          M.toList $+                          foldDocIdMap collectModules M.empty (docHits r)+  where+  collectModules ((DocInfo d _), _)  modules+                        = maybe modules (\fi -> M.insertWith (+) (takeWhile (/= '.') . moduleName $ fi) 1 modules) $+                          custom d++genPackages             :: Result FunctionInfo -> [(String, Int)]+genPackages r           = reverse $+                          L.sortBy (compare `on` snd) $+                          M.toList $+                          foldDocIdMap collectPackages M.empty (docHits r)+  where+  collectPackages ((DocInfo d _), _) packages+                        = maybe packages (\fi -> M.insertWith (+) (package fi) 1 packages) $+                          custom d++-- ----------------------------------------------------------------------------
+ src/Hayoo/Search/HTML.hs view
@@ -0,0 +1,243 @@+-- ----------------------------------------------------------------------------++{- |+  Module     : Hayoo.Search.HTML+  Copyright  : Copyright (C) 2008 Timo B. Huebel+  License    : MIT++  Maintainer : Timo B. Huebel (tbh@holumbus.org)+  Stability  : experimental+  Portability: portable+  Version    : 0.1++  The conversion functions for generating the HTML code of an Hayoo!+  search result.+ +-}++-- ----------------------------------------------------------------------------++{-# LANGUAGE OverloadedStrings #-}++module Hayoo.Search.HTML +    ( RenderState (..)+    , result+    )+where++import           Data.Char      ( toLower+                                , isSpace+                                )+import           Data.Function+import qualified Data.List      as L+import qualified Data.Map       as M+import           Data.Maybe+import           Data.Text      (Text, pack)++import           Text.XHtmlCombinators+import qualified Text.XHtmlCombinators.Attributes as A++import           Holumbus.Utility hiding (escape)+import           Holumbus.Index.Common++import           Holumbus.Query.Result++import           Hayoo.IndexTypes+import           Hayoo.Search.Common++data RenderState = RenderState+                 { rsQuery    :: String+                 , rsStart    :: Int+                 , rsStatic   :: Bool+                 }++pageLimit :: Int+pageLimit = 10++maxWordHits :: Int+maxWordHits = 75++staticRoot :: String+staticRoot = "hayoo.html"++text' :: (Functor t, Monad t, CData c) => String -> XHtmlT t c+text' = text . pack++onclick :: Text -> Attr+onclick = A.attr "onclick"++-- | Render the whole Hayoo! search result.+result :: RenderState -> StatusResult -> XHtml FlowContent+result rs (sm, rf, rp, tm, tp) = div' [A.id_ "result"] $ do+  div' [A.id_ "status"] $ text' sm  +  packageResults rs rp+  topLists rs tm tp+  functionResults rs rf+  div' [A.class_ "clear"] $ empty+  pager rs $ max (sizeDocHits rf) (sizeDocHits rp)++-- | The aggregation lists.+topLists :: RenderState -> [(String, Int)] -> [(String, Int)] -> XHtml FlowContent+topLists _ [] [] = empty+topLists rs tm tp = div' [A.id_ "aggregation"] $ do+  moduleList rs tm+  packageList rs tp++-- | The aggregated module list.+moduleList :: RenderState -> [(String, Int)] -> XHtml FlowContent+moduleList _ [] = empty+moduleList rs tm = div' [A.id_ "modules"] $ do+  div' [A.class_ "headline"] $ text "Top 15 Modules"+  mapM_ topModule (take 15 tm)+  where+  topModule (m, c) = div' [A.class_ "rootModule"] $ do+    a' [A.class_ "rootModuleName", A.href staticLink, onclick dynamicLink] $ text' m+    text " "+    span' [A.class_ "rootModuleCount"] $ text' $ show c+      where+      staticLink = pack $ staticRoot ++ "?query=" ++ (rsQuery rs) ++ "%20module%3A" ++ m+      dynamicLink = pack $ "addToQuery('module:" ++ m ++ "'); return false;"++-- | The aggregated package list.+packageList :: RenderState -> [(String, Int)] -> XHtml FlowContent+packageList _ [] = empty+packageList rs tp = div' [A.id_ "packages"] $ do+  div' [A.class_ "headline"] $ text "Top 15 Packages"+  mapM_ topPackage (take 15 tp)+  where+  topPackage (k, c) = div' [A.class_ "package"] $ do+    a' [A.class_ "packageLink", A.href staticLink, onclick dynamicLink] $ text' k+    text " "+    span' [A.class_ "packageCount"] $ text' $ show c+      where+      staticLink = pack $ staticRoot ++ "?query=" ++ (rsQuery rs) ++ "%20package%3A" ++ k+      dynamicLink = pack $ "addToQuery('package:" ++ k ++ "'); return false;"++-- | The Hackage hits (i.e. packages)+packageResults :: RenderState -> (Result PackageInfo) -> XHtml FlowContent+packageResults rs rp = let pl = extractPackages rp in +  if L.null pl then empty else div' [A.id_ "hackage"] $ do +    mapM_ packageInfo pl+  where+  packageInfo k = div' [A.class_ "package"] $ do+    div' [A.class_ "category"] $ mapM_ packageCategory (split "," $ p_category k)+    div' [A.class_ "name"] $ do+      a' [A.class_ "name", A.href $ pack $ "http://hackage.haskell.org/package/" ++ (p_name k)] $ +                                text' (p_name k)+      span' [A.class_ "synopsis"] $ text' (" " ++ (p_synopsis k))+    div' [A.class_ "description"] $ text' (p_description k)+    div' [A.class_ "author"] $ text' (p_author k)+  packageCategory c = a' [A.class_ "category", A.href $ pack $ "http://hackage.haskell.org/packages/archive/pkg-list.html#cat:" ++ map toLower c] $ text' c+  extractPackages = take pageLimit . drop (rsStart rs) . +                    (mapMaybe (\(_, (di, _)) -> custom $ document di)) . +                    reverse . L.sortBy (compare `on` (docScore . fst . snd)) . toListDocIdMap . docHits++-- | The function hits.+functionResults :: RenderState -> (Result FunctionInfo) -> XHtml FlowContent+functionResults rs rf = do+  div' [A.id_ "words"] $ mapM_ (wordInfo rs $ maxScoreWordHits rf) (toListSortedWords $ wordHits rf)+  div' [A.id_ "documents"] $ table $ mapM_ functionInfo (toListSortedDocs $ docHits rf)+    where+                notSignature (_, (_, wch)) = M.keys wch /= ["signature"]+                toListSortedDocs = take pageLimit . drop (rsStart rs) . reverse . +                        L.sortBy (compare `on` (docScore . fst . snd)) . toListDocIdMap+                toListSortedWords = L.sortBy (compare `on` fst) . take maxWordHits . +                        L.sortBy (compare `on` (wordScore .fst . snd)) . filter notSignature . M.toList++-- | Render the information about a function (module w/ link, function name, signature, ...)+functionInfo :: (DocId, (DocInfo FunctionInfo, DocContextHits)) -> XHtml Table1Content+functionInfo (_, (DocInfo (Document _ _ Nothing) _, _)) = error "Expecting custom document info"+functionInfo (_, (DocInfo (Document t u (Just fi)) r, _)) = tbody $ do+        tr' [A.class_ "function"] $ do+                td' [A.class_ "module"] $ do+                        a' [A.class_ "module", A.href $ pack (modLink u)] $ text' (moduleName fi ++ ".")+                td' [A.class_ "name"] $ do+                        a' [A.class_ "function", A.href $ pack u, A.title $ pack ("Score: " ++ show r)] $ text' t+                td' [A.class_ "signature"] $ do+                        sigDecl (signature fi)+        tr' [A.class_ "details"] $ do+                td' [A.class_ "package"] $ do+                        a' [A.class_ "package", A.href $ pack (pkgLink $ package fi)] $ text' (package fi)+                td' [A.class_ "description", A.colspan 2] $ div_ $ do+                        a' [A.class_ "toggleFold", onclick "toggleFold(this);"] $ empty+                        div' [A.class_ "description"] $ let f = fctDescr fi in+                                if L.null f then text "No description." else text' f+                        let c = sourceURI fi in if L.null c then empty else span' [A.class_ "source"] $ do+                                a' [A.class_ "source", A.href $ pack c] $ text "Source"+        where+        modLink = takeWhile ((/=) '#')+        pkgLink = (++) "http://hackage.haskell.org/package/"+        sigDecl s'+                | s' `elem` ["data", "type", "newtype", "class", "module"] = span' [A.class_ "declaration"] $ text' s+                | otherwise = text' $ replace "->" " -> " s+                where+                s = ":: " ++ stringTrim s'++-- | Render a word in the cloud of suggestions+wordInfo :: RenderState -> Score -> (Word, (WordInfo, WordContextHits)) -> XHtml FlowContent+wordInfo rs m (w, (WordInfo ts s, c)) = do+        a' [A.class_ "cloud", A.href staticLink, onclick dynamicLink, A.title $ pack origin] $ do+                span' [A.class_ $ pack $ "cloud" ++ (show ((round $ weightScore 1 9 m s)::Int))] $ text' w+        text " "+        where+        origin = join ", " $ M.keys c+        dynamicLink = pack $ "replaceInfQuery('" ++ escape t ++ "','" ++ escape w ++ "'); return false;"+        staticLink = pack $  staticRoot ++ "?query=" ++ (replace t w qu) ++ "&start=" ++ (show st)+        weightScore mi ma to v  = ma - ((to - v) / to) * (ma - mi)+        escape []      = []+        escape (x:xs)  = if x == '\'' then "\\'" ++ escape xs else x : (escape xs)+        t = head ts+        qu = rsQuery rs+        st = rsStart rs++pager :: RenderState -> Int -> XHtml FlowContent+pager rs m = if m <= 0 || m < pageLimit then empty else div'[A.id_ "pager"] $ do+                nav "previous" "<" (_prevPage pg)+                mapM_ page (_predPages pg)+                span' [A.class_ "current"] $ text' $ show $  _currPage pg+                mapM_ page (_succPages pg)+                nav "next" ">" (_nextPage pg)+        where+        pg = makePager (rsStart rs) pageLimit m+        nav :: Text -> String -> Maybe Int -> XHtml FlowContent+        nav _ _ Nothing = empty+        nav c t (Just v) = a' [A.class_ c, A.href $ statLink v, onclick $ dynLink v] $ text' t+        page (v, t)  = a' [A.class_ "page", A.href $ statLink v, onclick $ dynLink v] $ text' $ show t+        dynLink v = pack $ "showPage(" ++ show v ++ "); return false;"+        statLink v = pack $ staticRoot ++ "?query=" ++ (rsQuery rs) ++ "&start=" ++ show v++data Pager  = Pager +              { _prevPage  :: Maybe Int -- == last predPages+              , _predPages :: [(Int, Int)]+              , _currPage  :: Int+              , _succPages :: [(Int, Int)]+              , _nextPage  :: Maybe Int -- == head succPages+              }++-- Start element (counting from zero), elements per page, total number of elements.+makePager :: Int -> Int -> Int -> Pager+makePager s pg n = Pager pv (drop (length pd - 10) pd) (length pd + 1) (take 10 sc) nt+  where+  pv = if s < pg then Nothing else Just (s - pg)+  nt = if s + pg >= n then Nothing else Just (s + pg)+  pd = map (\x -> (x, x `div` pg + 1)) $ genPred s []+    where+    genPred rp tp = let np = rp - pg in+                                  if np < 0+                                  then tp+                                  else genPred np (np:tp)+  sc = map (\x -> (x, x `div` pg + 1)) $ genSucc s []+    where+    genSucc rs ts = let ns = rs + pg in+                                  if ns >= n+                                  then ts+                                  else genSucc ns (ts ++ [ns])++-- | Replace a given list with another list in a list.+replace :: Eq a => [a] -> [a] -> [a] -> [a]+replace old new l = L.intercalate new . split old $ l++-- | Remove leading and trailing whitespace using isSpace.+stringTrim :: String -> String+stringTrim = reverse . dropWhile isSpace . reverse . dropWhile isSpace+
+ src/Hayoo/Search/JSON.hs view
@@ -0,0 +1,98 @@+-- ----------------------------------------------------------------------------++{- |+  Module     : Hayoo.Search.JSON+  Copyright  : Copyright (C) 2010 Timo B. Huebel+  License    : MIT++  Maintainer : Timo B. Huebel (tbh@holumbus.org)+  Stability  : experimental+  Portability: portable+  Version    : 0.1++  Rendering a Hayoo! search result into a JSON string.++-}++-- ----------------------------------------------------------------------------++module Hayoo.Search.JSON+    ( renderJson+    , renderEmptyJson+    )+where++import qualified Data.Map as M++import Text.JSON++import Holumbus.Index.Common+import Holumbus.Query.Result++import Hayoo.IndexTypes+import Hayoo.Search.Common++-- ----------------------------------------------------------------------------++renderEmptyJson         :: String+renderEmptyJson         = encodeStrict $+                          jo +                          [ ("message", js "Please provide a query.")+                          , ("hits", showJSON (0 :: Int))+                          , ("functions", JSArray []) +                          , ("completions", JSArray []) +                          , ("modules", JSArray []) +                          , ("packages", JSArray []) +                          ]++renderJson              :: StatusResult -> String+renderJson (msg, res, _, mods, pkgs)+                        = encodeStrict $+                          jo+                          [ ("message",      js msg)+                          , ("hits",         showJSON      $ sizeDocHits res)+                          , ("functions" ,   buildDocHits  $ docHits res)+                          , ("completions" , buildWordHits $ wordHits res)+                          , ("modules",      buildTopList    mods)+                          , ("packages",     buildTopList    pkgs)+                          ]++buildDocHits            :: DocHits FunctionInfo -> JSValue+buildDocHits dh         = JSArray $ map buildDoc $ toListDocIdMap dh++buildDoc                :: (DocId, (DocInfo FunctionInfo, DocContextHits)) -> JSValue+buildDoc (_, (DocInfo (Document t u (Just fi)) _, _))+                        = jo+                          [ ("name",        js t)+                          , ("uri",         js u)+                          , ("module",      js $ moduleName fi)+                          , ("signature",   js $ signature fi)+                          , ("package",     js $ package fi)+                          , ("description", js $ fctDescr fi)+                          ]+buildDoc _              = error "Expected custom function info"++buildWordHits           :: WordHits -> JSValue+buildWordHits wh        = JSArray $ map buildWord (M.toList wh)++buildWord               :: (Word, (WordInfo, WordContextHits)) -> JSValue+buildWord (w, (WordInfo _ s, _))+                        = jo+                          [ ("word", js w)+                          , ("count", showJSON s)+                          ]++buildTopList            :: [(String, Int)] -> JSValue+buildTopList            = showJSON . map buildTopElem+  where+  buildTopElem (n, c)   = jo+                          [ ("name", js n)+                          , ("count", showJSON c)+                          ]++js                      :: String -> JSValue+js                      = JSString . toJSString++jo                      :: [(String, JSValue)] -> JSValue+jo                      = JSObject . toJSObject+
+ src/Hayoo/Search/Pages/Static.hs view
@@ -0,0 +1,268 @@+-- ----------------------------------------------------------------------------++{- |+  Module     : Hayoo.Search.Pages.Static+  Copyright  : Copyright (C) 2010 Timo B. Huebel+  License    : MIT++  Maintainer : Timo B. Huebel (tbh@holumbus.org)+  Stability  : experimental+  Portability: portable+  Version    : 0.1++  The main Hayoo! template.+-}++-- ----------------------------------------------------------------------------++{-# LANGUAGE OverloadedStrings #-}++module Hayoo.Search.Pages.Static (help, about, api, examples) where++import Data.Text (Text)++import Text.XHtmlCombinators+import qualified Text.XHtmlCombinators.Attributes as A++examples :: XHtml FlowContent+examples = div' [A.id_ "result"] $ do+  div' [A.id_ "status"] $ text "Enter some search terms above to start a search."+  div' [A.id_ "words"] $ text " "+  div' [A.id_ "documents"] $ div' [A.id_ "examples"] $ do+    text "Hayoo! will search all packages from "+    a' [A.href "http://hackage.haskell.org"] $ text "Hackage"+    text ", including all function and type definitions. Here are some example queries:"+    div' [A.class_ "example"] $ p $ do+      a' [A.attr "onclick" "replaceInQuery(&apos;&apos;,&apos;map&apos;); return false;", A.href "hayoo.html?query=map&amp;start=0"] $ text "map"+      text " searches for everything that contains a word starting with \"map\" (case insensitive) in the function name, module name or description."+    div' [A.class_ "example"] $ p $ do+      a' [A.attr "onclick" "replaceInQuery(&apos;&apos;,&apos;name:map&apos;); return false;", A.href "hayoo.html?query=name%3Amap&amp;start=0"] $ text "name:map"+      text " searches for everything where the function name starts with \"map\" (case insensitive)."+    div' [A.class_ "example"] $ p $ do+      a' [A.attr "onclick" "replaceInQuery(&apos;&apos;,&apos;map OR fold&apos;); return false;", A.href "hayoo.html?query=map%20OR%20fold&amp;start=0"] $ text "map OR fold"+      text " searches for everything that contains a word starting with \"map\" or \"fold\" (case insensitive) in the function name, module name or description."+    div' [A.class_ "example"] $ p $ do+      a' [A.attr "onclick" "replaceInQuery(&apos;&apos;,&apos;map package:containers&apos;); return false;", A.href "hayoo.html?query=map%20package%3Acontainers&amp;start=0"] $ text "map package:containers"+      text " searches for everything from package \"containers\" that contains a word starting with \"map\" (case insensitive) in the function name, module name or description."+    div' [A.class_ "example"] $ p $ do+      a' [A.attr "onclick" "replaceInQuery(&apos;&apos;,&apos;map hierarchy:Lazy&apos;); return false;", A.href "hayoo.html?query=map%20hierarchy%3ALazy&amp;start=0"] $ text "map hierarchy:Lazy"+      text " searches for everything where \"Lazy\" appears somewhere in the full qualified module name \+        \and that contains a word starting with \"map\" (case insensitive) in the function name, module name or description."+    div' [A.class_ "example"] $ p $ do+      a' [A.attr "onclick" "replaceInQuery(&apos;&apos;,&apos;(map OR fold) module:Data.Map&apos;); return false;", A.href "hayoo.html?query=(map%20OR%20fold)%20module%3AData.Map&amp;start=0"] $ text "(map OR fold) module:Data.Map"+      text " searches for everything from module \"Data.Map\" that contains a word starting with \"map\" or \"fold\" (case insensitive) in the function name, module name or description."+    div' [A.class_ "example"] $ p $ do+      a' [A.attr "onclick" "replaceInQuery(&apos;&apos;,&apos;name:attr module:Text.XML&apos;); return false;", A.href "hayoo.html?query=name%3Aattr%20module%3AText.XML&amp;start=0"] $ text "name:attr module:Text.XML"+      text " searches for everything from the whole module hierarchy \"Text.XML\" where the function name starts with \"attr\" (case insensitive)."++help :: XHtml FlowContent+help = div' [A.id_ "result"] $ do+  div' [A.id_ "status"] $ text "Enter some search terms above to start a search."++  div' [A.id_ "helptext", A.class_ "text"] $ do+    h2 $ text "Basic Usage"+    p $ do+      text "By default, Hayoo! searches for function names, module names, signatures and function \+        \descriptions. With every letter typed, Hayoo! will show the results it thinks are best matching \+        \the query as well as some suggestions on how the words from the query could be completed. \+        \Clicking one of these suggestions will replace the according word in the query."+    p $ do+      text "Hayoo! displays results as a list of functions, including full qualified module name and the \+        \function signature. Clicking the function name will lead directly to the corresponding documentation \+        \while clicking the module name will lead to the documentation of the module. Additionally, Hayoo! \+        \shows the function description (if available) and provides a link leading directly to the source \+        \of the function (if available). The description of the function can be expanded by clicking on \+        \the small '+' sign."+    p $ do+      text "Along with the results, Hayoo! shows two lists on the right, containing the top fifteen \+        \root-modules and packages. These are aggregated from the actual results. Clicking on each of \+        \these will further restrict the current query to the respective module hierarchy or package. \+        \On the left side, package search results are shown if the query matches the package information."++    h2 $ text "Advanced Queries"+    p $ do+      text "If words are seperated by whitespace, Hayoo! will search for results containing both words. \+        \Instead of using whitespace, the explicit "+      span' [A.class_ "query"] $ text "AND"+      text " operator can be used. Hayoo! also supports "+      span' [A.class_ "query"] $ text "OR"+      text " and "+      span' [A.class_ "query"] $ text "NOT"+      text " operators, although the "+      span' [A.class_ "query"] $ text "NOT"+      text " operator may only be used together with "+      span' [A.class_ "query"] $ text "AND"+      text ", e.g. "+      span' [A.class_ "query"] $ text "map NOT fold"+      text " or "+      span' [A.class_ "query"] $ text "map AND NOT fold"+      text ". Operator precedence can be influenced using round parentheses. Phrases can be searched \+        \using double quotes, e.g. "+      span' [A.class_ "query"] $ text "\"this is a phrase\""+      text "."+ +    p $ do+      text "It is possible to restrict a search to certain packages or modules. The most simple way would \+        \be to just include the package name in the search, e.g. "+      span' [A.class_ "query"] $ text "map base"+      text " will prefer hits from the base package. But the restriction can also be more explicit, like "+      span' [A.class_ "query"] $ text "map package:base"+      text " or like "+      span' [A.class_ "query"] $ text "map module:data.list"+      text ". It is also possible to specify several different modules or packages, like this: "+      span' [A.class_ "query"] $ text "fold module:(data.list OR data.map)"+      text ". This will return all hits for fold in the module hierarchies below Data.List and Data.Map."+    p $ do+      text "Hayoo! always performs fuzzy queries. This means, it tries to find something even if the \+        \query contains spelling errors. For example, Hayoo! will still find \"fold\" if \"fodl\" is \+        \being searched. If Hayoo! detects \">\" in the query string, it will only search for signatures. \+        \A signature query may consist of explicit type names as well as type variables. For example, \+        \searching for \"a > b\" will find signatures like \"Int > Bool\"."++    h2 $ text "Scope"+    p $ do+      text "Currently, Hayoo! searches all packages available on "+      a' [A.href "http://hackage.haskell.org"] $ text "Hackage"+      text ". Additionally, any Haskell documentation generated by Haddock can be included in Hayoo!. \+        \Just send a message including an URI where the documentation can be found to "+      a' [A.href "mailto:hayoo@holumbus.org"] $ text "hayoo@holumbus.org"+      text "."++about :: XHtml FlowContent+about = div' [A.id_ "result"] $ do+  div' [A.id_ "status"] $ text "Enter some search terms above to start a search."++  div' [A.id_ "abouttext", A.class_ "text"] $ do+    h2 $ text "About Hayoo!"+    p $ do+      text "Hayoo! is a search engine specialized on " +      a' [A.href "http://www.haskell.org"] $ text "Haskell"+      text " API documentation. The goal of Hayoo! is to provide an interactive, easy-to-use search interface to \+        \the documenation of various Haskell packages and libraries. Although the Hayoo! data is regularly updated, \+        \we might miss a package or library. If you think there is some documentation for Haskell modules available \+        \on the Internet which should be added to Hayoo!, just drop us a note at "+      a' [A.href "mailto:hayoo@holumbus.org"] $ text "hayoo@holumbus.org"+      text " and tell us the location where we can find the documentation."++    h2 $ text "Background"+    p $ do+      text "Hayoo! is an example application of the "+      a' [A.href "http://holumbus.fh-wedel.de"] $ text "Holumbus"+      text " framework and was heavily inspired by "+      a' [A.href "http://www.haskell.org/hoogle"] $ text "Hoogle"+      text ". The Holumbus library provides the search and indexing backend for Hayoo!. Holumbus and Hayoo! \+        \have been developed by Sebastian M. Gauck and Timo B. Hübel at "+      a' [A.href "http://www.fh-wedel.de"] $ text "FH Wedel University of Applied Sciences"+      text ". The Holumbus framework provides the basic building blocks for creating highly customizable search \+        \engines. To demonstrate the flexibility of the framework by a very special use case, the Hayoo! Haskell \+        \API search was implemented using Holumbus."+    p $ do+      text "Currently, Hayoo! is still in beta stage. This means, it can become unavailable unexpectedly, as \+        \we do some maintenance or add new features. Therefore you should not yet rely on Hayoo! as primary \+        \ search engine for Haskell documentation."+    p $ do+      text "Hardware infrastructure for daily index updates is generously sponsored by "+      a' [A.href "http://www.fortytools.com"] $ text "fortytools gmbh"+      text ", your friendly Haskell web development company."++    h2 $ text "Technical Information"+    p $ do+      text "Hayoo! is written entirely in Haskell and consists of two main parts: The indexer, which regularly \+        \checks Hackage for package updates and builds the search index and the web frontend, which relies on \+        \Apache, FastCGI and Hack for presenting search results to the user."++    h2 $ text "Feedback"+    p $ do+      text "We would like to know what you think about Hayoo!, therefore you can reach us at "+      a' [A.href "mailto:hayoo@holumbus.org"] $ text "hayoo@holumbus.org"+      text " and tell us about bugs, suggestions or anything else related to Hayoo!."+    div' [A.id_ "sponsors"] $ do+      div' [A.id_ "hol"] $ do+        a' [A.href "http://holumbus.fh-wedel.de"] $ img' "" "" [A.src "hayoo/hol.png", A.alt "Holumbus logo", A.class_ "logo"] -- Change here when img bug in xhtml-combinators is fixed+      div' [A.id_ "ft"] $ do+        a' [A.href "http://www.fortytools.com"] $ img' "" "" [A.src "hayoo/ft.png", A.alt "fortytools logo", A.class_ "logo"]+      div' [A.id_ "fhw"] $ do+        a' [A.href "http://www.fh-wedel.de"] $ img' "" "" [A.src "hayoo/fhw.gif", A.alt "FH-Wedel logo", A.class_ "logo"]+      +api :: XHtml FlowContent+api = div' [A.id_ "result"] $ do+  div' [A.id_ "status"] $ text "Enter some search terms above to start a search."++  div' [A.id_ "helptext", A.class_ "text"] $ do+    h2 $ text "Hayoo! API"+    p $ do+      text "Hayoo! provides a JSON-based webservice API, which can be used to retrieve search results in a structured \+           \format. This allows one to include Hayoo! search functionality in other applications. Arbitrary queries \+           \can be submitted the same way as they would be entered them into the search box and results are returned \+           \encoded in JSON format."++    p $ do+      text "You may use this service for whatever you like and without any limitations, although we would be \+           \very happy to know about any application that uses the Hayoo! webservice API. Just drop us a line at"+      a' [A.href "mailto:hayoo@holumbus.org"] $ text "hayoo@holumbus.org"+      text "."      ++    h2 $ text "Request URI"+    p $ text "Direct your search request to the following URI:"+    pre $ text "http://holumbus.fh-wedel.de/hayoo/hayoo.json?query=YOUR_QUERY"+    p $ do+      text "Provide your query as argument to the "+      code $ text "query"+      text " URI parameter. Please note that you need to ensure proper URI encoding for the query argument. The syntax \+           \for the query is the same as if it would be entered into the search box. A detailed explanation of the \+           \syntax can be found " +      a' [A.href "help.html"] $ text "here"+      text "."++    h2 $ text "Response"+    p $ do+      text "The response to a search request will be encoded in "+      a' [A.href "http://www.json.org"] $ text "JSON"+      text " format and is structured as follows:"+    pre $ do+      code $ text "{\n\+                 \  \"message\":\"Found 12 results and 17 completions.\",\n\+                 \  \"hits\":12,\n\+                 \  \"functions\":[ {\n\+                 \    \"name\":\"map\",\n\+                 \    \"uri\":\"http://hackage.haskell.org/...\",\n\+                 \    \"module\":\"Data.Map\",\n\+                 \    \"signature\":\"(a->b)->[a]->[b]\",\n\+                 \    \"package\":\"containers\"\n\+                 \  }, ... ],\n\+                 \  \"completions\":[ {\n\+                 \    \"word\":\"MapM\",\n\+                 \    \"count\":11\n\+                 \  }, ... ],\n\+                 \  \"modules\":[ {\n\+                 \    \"name\":\"Data\",\n\+                 \    \"count\":19\n\+                 \  }  }, ... ],\n\+                 \  \"packages\":[ {\n\+                 \    \"name\":\"containers\",\n\+                 \    \"count\":13\n\+                 \  }, ... ]\n\+                 \}"+    p $ do+      (text "The ") >> (ct "message") >> (text " field will contain a descriptive status message about the result \+        \or any errors encountered. The ") >> (ct "hits") >> (text " field will contain the total number of \+        \functions found. In the ") >> (ct "functions") >> (text " field, an array containing all functions found \+        \will be returned. For every function, a JSON object is included in the array.")+    p $ do+      (text "Each of these objects contains the function name, the URI pointing to the Haddock documentation, the module, \+        \the signature and the package name in the ") >> (ct "name") >> (text ", ") >> (ct "uri") >> (text ", ")+        >> (ct "module") >> (text ", ") >> (ct "signature") >> (text" and ") >> (ct "package") >> (text " fields, respectively.")+    p $ do+      (text "The ") >> (ct "completions") >> (text " contains all word completions (suggestions) resulting from the query \+        \For every completion, a JSON object is included in the array, containing the suggested word and the total number \+        \of occurrences of this word in the search result in the ") >> (ct "word") >> (text " and ") >> (ct "count")+        >> (text " fields.")+    p $ do+      (text "The ") >> (ct "modules") >> (text " and ") >> (ct "packages") >> (text " fields contain arrays with JSON objects \+        \denoting the occurrences of root modules and packages in the search result. For each element, the module/package \+        \name is included in the ") >> (ct "name") >> (text " field and the number of occurrences in the ") >> (ct "count")+        >> (text " field.")++ct :: (Functor t, Monad t, Inline c) => Text -> XHtmlT t c+ct = code . text+
+ src/Hayoo/Search/Pages/Template.hs view
@@ -0,0 +1,136 @@+-- ----------------------------------------------------------------------------++{- |+  Module     : Hayoo.Search.Pages.Template+  Copyright  : Copyright (C) 2010 Timo B. Huebel+  License    : MIT++  Maintainer : Timo B. Huebel (tbh@holumbus.org)+  Stability  : experimental+  Portability: portable+  Version    : 0.1++  The main Hayoo! template.+-}++-- ----------------------------------------------------------------------------++{-# LANGUAGE OverloadedStrings #-}++module Hayoo.Search.Pages.Template (Template, makeTemplate) where++import qualified Data.List as L+import qualified Data.Text as T++import Control.Category++import Text.XHtmlCombinators+import qualified Text.XHtmlCombinators.Attributes as A++type Template = XHtml FlowContent -> XHtml Page++-- | Make a Hayoo! template from the given package//function statistics.+makeTemplate :: Int -> Int -> Template+makeTemplate np nf template = html True $ do+  makeHead+  makeBody np nf template++makeHead :: XHtml TopLevelContent+makeHead = head_ $ do+  meta' "text/html; charset=UTF-8" [A.httpEquiv "content-type"]+  meta' "Haskell, API, Search, Hackage, Functions, Types, Packages" [A.name "keywords"]+  meta' "A Haskell API search engine with find-as-you-type and suggestions. Searches for function and type defintions in all Haskell packages from Hackage." [A.name "description", A.lang "en"]++  link' [A.type_ "image/ico", A.href "hayoo/favicon.ico", A.rel "Shortcut icon"]+  link' [A.type_ "text/css", A.href "hayoo/hayoo.css", A.rel "stylesheet"]+  link' [A.type_ "application/opensearchdescription+xml", A.href "hayoo/opensearch.xml", A.rel "search"]++  title "Hayoo! - Haskell API Search"++  script' "text/javascript" [A.src "hayoo/prototype.js"] "&nbsp;"+  script' "text/javascript" [A.src "hayoo/hayoo.js"] "&nbsp;"++  -- Piwik tracking stuff+  script "text/javascript" +    "var pkBaseURL = (('https:' == document.location.protocol) ? 'https://piwik.hayoo.info/' : 'http://piwik.hayoo.info/'); \+    \document.write(unescape(\"%3Cscript src='\" + pkBaseURL + \"piwik.js' type='text/javascript'%3E%3C/script%3E\"));"+  script "text/javascript"+    "try {\+      \var piwikTracker = Piwik.getTracker(pkBaseURL + \"piwik.php\", 3);\+      \piwikTracker.trackPageView();\+      \piwikTracker.enableLinkTracking();\+    \} catch( err ) {}"++makeInfo :: XHtml FlowContent+makeInfo = div' [A.id_ "info"] $ do+  a' [A.href "help.html"] $ text "Help"+  text " | "+  a' [A.href "about.html"] $ text "About"+  text " | "+  a' [A.href "api.html"] $ text "API"+  text " | "+  a' [A.href "/blog"] $ text "Blog"+  text " | "+  a' [A.href "http://hackage.haskell.org"] $ text "Hackage"+  text " | "+  a' [A.href "http://www.haskell.org"] $ text "Haskell"++makeQuery :: Int -> Int -> XHtml FlowContent+makeQuery np nf = div' [A.id_ "query"] $ do+  div' [A.id_ "logo"] $ a' [A.href "hayoo.html"] $ img' "" "" [A.src "hayoo/hayoo.png", A.alt "Hayoo! logo", A.class_ "logo"]+  form' "hayoo.html" [A.id_ "queryform", A.attr "onsubmit" "return forceProcessQuery()", A.method "get"] $ div' [A.id_ "queryinterface"] $ do+    input' [A.id_ "querytext", A.type_ "text", A.attr "onkeyup" "tryProcessQuery()", A.name "query", A.attr "autocomplete" "off", A.value ""]+    input' [A.id_ "querybutton", A.type_ "submit", A.value "Search"]+    img' "" "" [A.src "hayoo/loader.gif", A.alt "Throbber", A.id_ "throbber", A.style "display:none;"]+    div' [A.class_ "stats"] $ do+      text $ T.concat ["Concurrently search more than ", T.pack $ showCnt np, " packages and more than ", T.pack $ showCnt nf, " functions!"]++makeCredits :: XHtml FlowContent+makeCredits = div' [A.id_ "credits"] $ do+  div' [A.id_ "powered"] $ do+    div' [A.id_ "libs"] $ do+      text "Powered by " +      a' [A.class_ "credits", A.href "http://www.haskell.org"] $ text "Haskell"+      text ", "+      a' [A.class_ "credits", A.href "http://www.fh-wedel.de/~si/HXmlToolbox/"] $ text "HXT"+      text " and "+    a' [A.href "http://holumbus.fh-wedel.de"] $ img' "" "" [A.src "hayoo/holumbus.png", A.alt "Holumbus logo", A.class_ "logo"]++  div' [A.id_ "authors"] $ do+    div' [A.id_ "feedback"] $ do+      text "Please send any feedback to "+      a' [A.href "mailto:hayoo@holumbus.org"] $ text "hayoo@holumbus.org"+    div' [A.id_ "copyright"] $ do+      text "Hayoo! beta 2.2 © 2010 "+      span' [A.class_ "author"] $ a' [A.href "http://tbh.github.com"] $ text "Timo B. Hübel"+      text ", "+      span' [A.class_ "author"] $ text "Sebastian M. Gauck"+      text " & "+      span' [A.class_ "author"] $ text "Uwe Schmidt"++makeBody :: Int -> Int -> XHtml FlowContent -> XHtml TopLevelContent+makeBody np nf content = body $ do+  div' [A.id_ "container"] $ do+    makeInfo+    makeQuery np nf+    content+    makeCredits+    makeTracking++makeTracking :: XHtml FlowContent+makeTracking = +  noscript $ do+    p $ do+      img' "" "" [A.src "http://piwik.hayoo.info/piwik.php?idsite=3", A.alt "", A.style "border:0"]++showCnt         :: Int -> String+showCnt         = show >>> fmtCnt+    where+    fmtCnt      = reverse >>> insDot >>> reverse+    insDot s+        | L.null y      = s+        | otherwise     = x ++ "." ++ insDot y+        where+        (x , y) = splitAt 3 s++
+ src/Hayoo/Search/Parser.hs view
@@ -0,0 +1,208 @@+-- ----------------------------------------------------------------------------++{- |+  Module     : Hayoo.Parser+  Copyright  : Copyright (C) 2008 Timo B. Huebel+  License    : MIT++  Maintainer : Timo B. Huebel (tbh@holumbus.org)+  Stability  : experimental+  Portability: portable+  Version    : 0.2++  The parser for the Hayoo! web search.+ +-}++-- ----------------------------------------------------------------------------++module Hayoo.Search.Parser where++-- import qualified Data.Map as M+-- import Data.Char++import Hayoo.Signature++-- import Holumbus.Utility+import Holumbus.Query.Language.Grammar++import Text.ParserCombinators.Parsec++-- ------------------------------------------------------------++{- Uwe: done ++-- TODO TH This normalize and strip have to be shared by indexer and search!++-- | Normalizes a Haskell signature, e.g. @String -> Int -> Int@ will be transformed to +-- @a->b->b@. All whitespace will be removed from the resulting string.+normalizeSignature :: String -> String+normalizeSignature = join "->" . (replaceTypes M.empty ['a'..'z']) . split "->" . filter (not . isSpace)+  where+  replaceTypes _ _ [] = []+  replaceTypes v t (x:xs) = let (nv, ut, rx) = replace' in rx:(replaceTypes nv ut xs)+    where+    replace' = let ut = [head t] in maybe (M.insert r ut v, tail t, ut) (\n -> (v, t, n)) (M.lookup r v)+      where r = stripWith (\c -> (c == '(') || (c == ')')) x++-- | Strip unneeded whitespace from a signature, e.g. @String -> Map k a -> Int@ will be transformed+-- to @String->Map k a->Int@.+stripSignature :: String -> String+stripSignature = sep "->" . lsep "(" . rsep ")" . sep "." . sep "=>"+  where+  sep s = join s . map strip . split s+  lsep s = join s . map stripl . split s+  rsep s = join s . map stripr . split s+-}++-- ------------------------------------------------------------++-- | Parse a query using the special Hayoo! syntax.+parseQuery :: String -> Either String Query+parseQuery s+    | isSignature s     = Right sigQuery+    | otherwise         = result . (parse query "") $ s+  where+  sigQuery              = BinQuery Or+                          (Specifier ["signature"]  (Word $ stripSignature     s))+                          (Specifier ["normalized"] (Word $ normalizeSignature s))++  result (Left _e)      = Right (FuzzyWord s)+  result (Right q)      = Right q++-- | A query may always be surrounded by whitespace+query :: Parser Query+query = spaces >> andQuery+-- query = spaces >> ((try sigQuery) <|> (andQuery))++-- | Parse an and query.+andQuery :: Parser Query+andQuery = do t <- orQuery+              try (andOp' t) <|> return t+  where+  andOp' r = do andOp+                q <- (notQuery <|> andQuery)+                return (BinQuery And r q)++-- | Parse an or query.+orQuery :: Parser Query+orQuery = do t <- contextQuery+             do orOp+                q <- orQuery+                return (BinQuery Or t q)+                <|> return t++-- | Parse a negation.+notQuery :: Parser Query+notQuery = do notOp+              q <- contextQuery+              return (Negation q)++-- | Parse a context query.+contextQuery :: Parser Query+contextQuery = try contextQuery' <|> parQuery+  where+  contextQuery' = do c <- contexts+                     spaces+                     _ <- char ':'+                     spaces+                     t <- parQuery+                     return (Specifier c t)++-- | Parse a query surrounded by parentheses.+parQuery :: Parser Query+parQuery = parQuery' <|> phraseQuery <|> wordQuery+  where+  parQuery' = do _ <- char '('+                 spaces+                 q <- andQuery+                 spaces+                 _ <- char ')'+                 return q++-- | Parse a phrase query.+phraseQuery :: Parser Query+phraseQuery = do p <- phrase+                 return (Phrase p)++-- | Parse a word query.+wordQuery :: Parser Query+wordQuery = do w <- word+               return (FuzzyWord w)+{-+-- | Parse a signature.+sigQuery :: Parser Query+sigQuery = do+           r <- contains "->"+           s <- return (stripSignature r)+           n <- return (normalizeSignature r)+           return $ BinQuery Or (Specifier ["signature"] (Word s)) (Specifier ["normalized"] (Word n))+-}+contains :: String -> Parser String+contains s = do+             pr <- many1 (noneOf s)+             _ <- string s+             po <- many1 anyChar+             return (pr ++ s ++ po)++-- | Parse an and operator.+andOp :: Parser ()+andOp = (try andOp') <|> spaces1+  where+  andOp' = do spaces+              _ <- string "AND" +              spaces1+              return ()++-- | Parse an or operator.+orOp :: Parser ()+orOp = try orOp'+  where+  orOp' = do spaces+             _ <- string "OR"+             spaces1+             return ()++-- | Parse a not operator.+notOp :: Parser ()+notOp = try notOp'+  where+  notOp' = do spaces+              _ <- string "NOT" +              spaces1+              return ()++-- | Parse a word.+word :: Parser String+word = many1 wordChar++-- | Parse a phrase.+phrase :: Parser String+phrase = do _ <- char '"'+            p <- many1 phraseChar+            _ <- char '"'+            return p++-- | Parse a character of a word.+wordChar :: Parser Char+wordChar = noneOf "\")( "++-- | Parse a character of a phrases.+phraseChar :: Parser Char+phraseChar = noneOf "\""++-- | Parse a list of contexts.+contexts :: Parser [String]+contexts = context `sepBy1` (char ',')++-- | Parse a context.+context :: Parser String+context = do spaces +             c <- (many1 alphaNum)+             spaces+             return c++-- | Parse at least on white space character.+spaces1 :: Parser ()+spaces1 = skipMany1 space+
+ src/Hayoo/Signature.hs view
@@ -0,0 +1,96 @@+{-# OPTIONS #-}++-- ------------------------------------------------------------++module Hayoo.Signature+where++import           Control.Arrow++import           Data.List++import           Holumbus.Crawler.Util  ( tokenize+                                        , match+                                        , split+                                        )++-- ------------------------------------------------------------++getSignature                    :: String -> String+getSignature                    = split "(.*(=>|::))?(\\s)*" >>> snd++-- | Strip redundant whitespace from a signature, e.g. @String -> Map k a -> Int@ will be transformed+-- to @String->Map k a->Int@.++stripSignature                  :: String -> String+stripSignature                  = tokenizeSignature >>> joinSignatureTokens++-- | Normalizes a Haskell signature, e.g. @String -> Int -> Int@ will be transformed to +-- @a->b->b@. All whitespace will be removed from the resulting string.++normalizeSignature              :: String -> String+normalizeSignature              = tokenizeSignature >>> normSignatureIds >>> joinSignatureTokens++-- | Tokenize a signature string++tokenizeSignature               :: String -> [String]+tokenizeSignature               = tokenize sigToken+    where+    sigToken                    = "[\\]\\[(,)]|->|=>|\\*|" ++ sigIdent++ascSym                          :: String+ascSym                          = "[-!#$%&*+./<=>?@\\\\^|~]"++uniSym                          :: String+uniSym                          = "[\\p{S}\\p{P}-[(),;\\[\\]`{}_:'\"]]"++uniSymNoColon                   :: String+uniSymNoColon                   = "[\\p{S}\\p{P}-[(),;\\[\\]`{}_:'\"]]"++conSym                          :: String+conSym                          = ":(" ++ uniSymNoColon ++ "|" ++ uniSym ++ "{2,})"     -- somewhat tricky tricky (::) isn't a conSym++sigIdent                        :: String+sigIdent                        = "[\\p{L}#][\\p{L}\\{N}'_.]*"++haskIdent                       :: String+haskIdent                       = "[\\p{L}][\\p{L}\\{N}'_.]*"++varIdent                        :: String+varIdent                        = "[\\p{Ll}][\\p{L}\\{N}'_.]*"++typeIdent                       :: String+typeIdent                       = "[\\p{Lu}#][\\p{L}\\{N}'_.]*" -- "|" ++ conSym        -- but this requires latest hxt-8.5.3, beacuse of a parser error in uniSym expression++joinSignatureTokens             :: [String] -> String+joinSignatureTokens             = mapAccumL insBlank False >>> snd >>> concat+    where+    insBlank isId x             = (isId'+                                  , if isId && isId'+                                    then ' ' : x+                                    else       x+                                  )+        where+        isId'                   = match sigIdent x++normSignatureIds                :: [String] -> [String]+normSignatureIds                = mapAccumL renId ([], ['a'..]) >>> snd+    where+    renId ac@(env, ids) x+        | isId x                = case lookup x env of+                                  Nothing       -> ( ((x, head ids) : env, tail ids)+                                                   , [head ids]+                                                   )+                                  Just c        -> (ac, [c])+        | otherwise             = (ac, x)+        where+        isId                    = match varIdent++isSignature                     :: String -> Bool+isSignature                     = match $ anyWord ++ "(->|" ++ tupleType ++ "|" ++ listType ++ ")" ++ anyWord+    where+    anyWord                     = "(.|\\n|\\r)*"+    listType                    = "\\[" ++ anyWord ++ "\\]"+    tupleType                   = "\\((" ++ anyWord ++ "," ++ anyWord ++ ")?\\)"++-- ------------------------------------------------------------
+ src/Hayoo/Snap/Application.hs view
@@ -0,0 +1,54 @@+{-++This module defines our application's monad and any application-specific+information it requires.++-}++module Hayoo.Snap.Application+  ( Application+  , ApplicationState(..)+  , applicationInitializer+  ) where++import           Snap.Extension++import           Hayoo.Snap.Extension.HayooState++------------------------------------------------------------------------------+-- | 'Application' is our application's monad. It uses 'SnapExtend' from+-- 'Snap.Extension' to provide us with an extended 'MonadSnap' making use of+-- the Heist and Timer Snap extensions.+type Application = SnapExtend ApplicationState+++------------------------------------------------------------------------------+-- | 'ApplicationState' is a record which contains the state needed by the Snap+-- extensions we're using.  We're using Heist so we can easily render Heist+-- templates, and Timer simply to illustrate the config loading differences+-- between development and production modes.++data ApplicationState = ApplicationState+    { hayooState    :: HayooState+    }++------------------------------------------------------------------------------++instance HasHayooState ApplicationState where+    getHayooState     = hayooState+    setHayooState s a = a { hayooState = s }+++------------------------------------------------------------------------------+-- | The 'Initializer' for ApplicationState. For more on 'Initializer's, see+-- the documentation from the snap package. Briefly, this is used to+-- generate the 'ApplicationState' needed for our application and will+-- automatically generate reload\/cleanup actions for us which we don't need+-- to worry about.++applicationInitializer :: Initializer ApplicationState+applicationInitializer = do+    hayoo <- hayooInitializer+    return $ ApplicationState hayoo++------------------------------------------------------------------------------
+ src/Hayoo/Snap/Extension/HayooState.hs view
@@ -0,0 +1,132 @@+{-# LANGUAGE OverloadedStrings #-}++{-|++'Snap.Extension.Timer.Impl' is an implementation of the 'MonadTimer'+interface defined in 'Snap.Extension.Timer'.++As always, to use, add 'TimerState' to your application's state, along with an+instance of 'HasTimerState' for your application's state, making sure to use a+'timerInitializer' in your application's 'Initializer', and then you're ready to go.++This implementation does not require that your application's monad implement+interfaces from any other Snap Extension.++-}++module Hayoo.Snap.Extension.HayooState+  ( HayooState(..)+  , HasHayooState(..)+  , MonadHayoo(..)+  , hayooInitializer+  ) where++import Control.Monad.Reader++import Hayoo.IndexTypes                 ( buildRankTable )+import Hayoo.Search.EvalSearch+import Hayoo.Search.Pages.Template      ( makeTemplate )++import Holumbus.Index.Common++import Snap.Extension+import Snap.Types++import System.IO                        ( stderr+                                        , hPutStrLn+                                        )++------------------------------------------------------------------------------+-- | Your application's state must include a 'TimerState' in order for your+-- application to be a 'MonadTimer'.++newtype HayooState = HayooState+    { getCore :: Core+    }++------------------------------------------------------------------------------+-- | For your application's monad to be a 'MonadTimer', your application's+-- state needs to be an instance of 'HasTimerState'. Minimal complete+-- definition: 'getTimerState', 'setTimerState'.++class HasHayooState s where+    getHayooState :: s -> HayooState+    setHayooState :: HayooState -> s -> s+++------------------------------------------------------------------------------+-- | The 'MonadHayoo' type class. Minimal complete definition: 'hayooCore'.++class MonadSnap m => MonadHayoo m where+    -- | The core Hayoo state which was last loaded.+    hayooCore :: m Core++------------------------------------------------------------------------------++instance HasHayooState s => MonadHayoo (SnapExtend s) where+    hayooCore = fmap getCore $ asks getHayooState++------------------------------------------------------------------------------++instance (MonadSnap m, HasHayooState s) => MonadHayoo (ReaderT s m) where+    hayooCore = fmap getCore $ asks getHayooState++------------------------------------------------------------------------------++instance InitializerState HayooState where+    extensionId = const "Hayoo/HayooState"+    mkCleanup   = const $ return ()+    mkReload    = const $ return ()++------------------------------------------------------------------------------+-- | The Initializer for 'HayooState'. No arguments are required.++hayooInitializer :: Initializer HayooState+hayooInitializer = liftIO getHayooInitialState >>= mkInitializer . HayooState++------------------------------------------------------------------------------++ixBase          :: FilePath+ixBase          = "./lib"++getHayooInitialState    :: IO Core+getHayooInitialState+  = do+    idx  <- loadIndex     hayooIndex+    infoM "Hayoo.Main" ("Hayoo index   loaded from file " ++ show hayooIndex)++    doc  <- loadDocuments hayooDocs+    infoM "Hayoo.Main" ("Hayoo docs    loaded from file " ++ show hayooDocs )+    infoM "Hayoo.Main" ("Hayoo docs contains " ++ show (sizeDocs doc) ++ " functions and types")++    pidx <- loadIndex     hackageIndex+    infoM "Hayoo.Main" ("Hackage index loaded from file " ++ show hackageIndex)++    pdoc <- loadPkgDocs   hackageDocs+    infoM "Hayoo.Main" ("Hackage docs  loaded from file " ++ show hackageDocs)+    infoM "Hayoo.Main" ("Hackage docs contains " ++ show (sizeDocs pdoc) ++ " packages")++    prnk <- return $ buildRankTable pdoc+    infoM "Hayoo.Main" ("Hackage package rank table computed")++    tpl  <- return $ makeTemplate (sizeDocs pdoc) (sizeDocs doc)++    return $ Core+             { index      = idx+             , documents  = doc+             , pkgIndex   = pidx+             , pkgDocs    = pdoc+             , template   = tpl+             , packRank   = prnk+             }+  where+  hayooIndex      = ixBase ++ "/ix.bin.idx"+  hayooDocs       = ixBase ++ "/ix.bin.doc"+  hackageIndex    = ixBase ++ "/pkg.bin.idx"+  hackageDocs     = ixBase ++ "/pkg.bin.doc"++  infoM m msg     = hPutStrLn stderr $ m ++ ": " ++ msg++------------------------------------------------------------------------------++
+ src/Hayoo/Snap/Site.hs view
@@ -0,0 +1,173 @@+{-# LANGUAGE OverloadedStrings #-}++------------------------------------------------------------------------------++{-|++This is where all the routes and handlers are defined for your site. The+'site' function combines everything together and is exported by this module.++-}++------------------------------------------------------------------------------++module Hayoo.Snap.Site+  ( site+  ) where++import           Control.Applicative++import qualified Data.ByteString.Char8          as C+import qualified Data.Map                       as M+import           Data.Maybe+import qualified Data.Text                      as T+import qualified Data.Text.Encoding             as T++import           Hayoo.Search.EvalSearch        ( Core+                                                , template+                                                , getValDef+                                                , readDef++                                                , parseQuery+                                                , genResult+                                                , emptyRes++                                                , renderJson+                                                , renderEmptyJson++                                                , examples+                                                , filterStatusResult+                                                )+import           Hayoo.Search.HTML              ( RenderState(..)+                                                , result+                                                )+import qualified Hayoo.Search.Pages.Static      as P++import           Hayoo.Snap.Extension.HayooState+import           Hayoo.Snap.Application++import           System.FilePath                ( (</>) )++import           Snap.Util.FileServe+import           Snap.Types++import qualified Text.XHtmlCombinators          as X+++------------------------------------------------------------------------------+-- | The main entry point handler.++site :: Application ()+site = route [ ("/",             ifTop hayooHtml)       -- map to /hayoo.html+             , ("/hayoo.html",   hayooHtml)+             , ("/hayoo.json",   hayooJson)+             , ("/help.html",    serveStatic P.help)+             , ("/about.html",   serveStatic P.about)+             , ("/api.html",     serveStatic P.api)+             , ("/hayoo/:stuff", serveHayooStatic)+             ]+       <|> serveDirectory "hayoo"++------------------------------------------------------------------------------+-- | Deliver Hayoo files++serveHayooStatic :: Application ()+serveHayooStatic = do+    relPath <- decodedParam "stuff"+    serveFile $ "hayoo" </> C.unpack relPath+  where+    decodedParam p = fromMaybe "" <$> getParam p++------------------------------------------------------------------------------+-- | Deliver static Hayoo pages++serveStatic :: X.XHtml X.FlowContent -> Application ()+serveStatic pg+  = do+    core <- hayooCore +    putResponse htmlResponse+    writeText (X.render $ (template core) pg) ++------------------------------------------------------------------------------+-- | Render JSON page++hayooJson :: Application ()+hayooJson = do+    pars <- getParams+    core <- hayooCore+    putResponse myResponse+    writeText (T.pack $ evalJsonQuery (toStringMap pars) core)+  where+  myResponse = setContentType "application/json; charset=utf-8"+               . setResponseCode 200+               $ emptyResponse+  toStringMap   = map (uncurry tos) . M.toList+      where+      tos k a   = (            T.unpack . T.decodeUtf8  $ k+                  , concatMap (T.unpack . T.decodeUtf8) $ a+                  )++evalJsonQuery :: [(String, String)] -> Core -> String+evalJsonQuery p idct+    | null request      = renderEmptyJson+    | otherwise         = renderResult+    where+    request =               getValDef p "query"  ""++    {- not used for json output+    start   = readDef 0    (getValDef p "start"  "")+    static  = readDef True (getValDef p "static" "")+    tmpl    = template idct+    -}++    renderResult        = renderJson+                          . either emptyRes (genResult idct)+                          . parseQuery+                          $ request++------------------------------------------------------------------------------+-- | Render HTML page++hayooHtml :: Application ()+hayooHtml = do+    pars <- getParams+    core <- hayooCore+    putResponse htmlResponse+    writeText $ evalHtmlQuery (toStringMap pars) core+  where+  toStringMap   = map (uncurry tos) . M.toList+      where+      tos k a   = (            T.unpack . T.decodeUtf8  $ k+                  , concatMap (T.unpack . T.decodeUtf8) $ a+                  )++htmlResponse :: Response+htmlResponse = setContentType "text/html; charset=utf-8"+               . setResponseCode 200+               $ emptyResponse++evalHtmlQuery :: [(String, String)] -> Core -> T.Text+evalHtmlQuery p idct+    | null request      = renderEmptyHtml+    | otherwise         = renderResult+    where+    request =               getValDef p "query"  ""+    start   = readDef 0    (getValDef p "start"  "")+    static  = readDef True (getValDef p "static" "")+    tmpl    = template idct++    renderEmptyHtml     = X.render $ tmpl examples++    renderResult        = applyTemplate (RenderState request start static)+                          . filterStatusResult request+                          . either emptyRes (genResult idct)+                          . parseQuery+                          $ request+        where+        applyTemplate rs sr+            | rsStatic rs       = X.render $ tmpl rr+            | otherwise         = X.render $      rr+            where+            rr                  = result rs sr++------------------------------------------------------------------------------
+ src/Hayoo/URIConfig.hs view
@@ -0,0 +1,147 @@+{-# OPTIONS #-}++-- ------------------------------------------------------------++module Hayoo.URIConfig+    ( hayooStart+    , hayooRefs+    , hayooGetPackage+    , hayooPackageNames++    , hackagePackages+    , hackageStart+    , isHaddockURI++    , editLatestPackage++    , packageVersion+    , packageVersion'+    , packageVersion''++    , fileName+    )+where++-- import           Control.Applicative++import           Data.List++import           Holumbus.Crawler++import           Text.XML.HXT.Core++-- ------------------------------------------------------------++hayooStart                      :: [URI]+hayooStart                      = hackageStart++hayooRefs                       :: Bool -> [String] -> URI -> Bool+hayooRefs                       = hackageRefs++hayooGetPackage                 :: String -> String+hayooGetPackage                 = hackageGetPackage++hackageHome                     :: String+hackageHome                     = "http://hackage.haskell.org/packages/"++hackagePackages                 :: String+hackagePackages                 = "http://hackage.haskell.org/package/"         -- no "s" at the end !!!++hackageStartPage                :: URI+hackageStartPage                =  hackageHome ++ "archive/pkg-list.html"++hackageStart                    :: [URI]+hackageStart                    =  [ hackageStartPage ]++hackageRefs                     :: Bool -> [String] -> URI -> Bool+hackageRefs withDoc pkgs        = simpleFollowRef'+                                  ( (hackagePackages ++ packageName')+                                    : ( if withDoc+                                        then [ packageDocPath  ++ modulePath ++ ext "html" ]+                                        else []+                                      )+                                  )+                                  [ packageDocPath ++ alternatives+                                                       [ "doc-index.*" ++ ext "html"            -- no index files+                                                       , "src/.*"                               -- no hscolored sources+                                                       ]+                                  , hackagePackages ++ packageName' ++ packageVersion''         -- no package pages with (old) version numbers+                                  ]+    where+    packageDocPath              = hackagePackageDocPath ++ packageName' ++ "/" ++ packageVersion' ++ "/doc/html/"++    packageName'+        | null pkgs             = fileName+        | otherwise             = alternatives pkgs++hackagePackageDocPath           :: String+hackagePackageDocPath           = hackageHome ++ "archive/"++hackageGetPackage               :: String -> String+hackageGetPackage u+    | hackagePackageDocPath `isPrefixOf` u+                                = takeWhile (/= '/') . drop (length hackagePackageDocPath) $ u+    | otherwise                 = ""++getHackagePackage               :: String -> String+getHackagePackage s+    | match (hackagePackages ++ packageName) s+                                = drop (length hackagePackages) s+    | otherwise                 = ""++isHaddockURI                    :: URI -> Bool+isHaddockURI                    = match (hackagePackageDocPath ++ fileName ++ "/.+/doc/html/.+[.]html")++-- ------------------------------------------------------------++-- common R.E.s++alternatives                    :: [String] -> String+alternatives                    = ("(" ++) . (++ ")") . intercalate "|"++moduleName                      :: String+moduleName                      = "[A-Z][A-Za-z0-9_]*"++modulePath                      :: String+modulePath                      = moduleName ++ "(-" ++ moduleName ++ ")*"++fileName                        :: String+fileName                        = "[^/?]+"++ext                             :: String -> String+ext                             = ("[.]" ++)++packageName                     :: String+packageName                     = "[A-Za-z]([A-Za-z0-9_]|-)*"++packageVersion+  , packageVersion'+  , packageVersion''            :: String++packageVersion                  = "[0-9]+([.][0-9]+)*"          -- there are package versions without sub version no+packageVersion''                = "-" ++ packageVersion+packageVersion'                 = alternatives [packageVersion, "latest"]++-- ------------------------------------------------------------++-- In the package doc URIs the package version number "/*.*.*/" is substituted by the alias "/latest/"++editLatestPackage               :: String -> String+editLatestPackage               = sed (const "/latest/") "/[0-9.]+/"++-- ------------------------------------------------------------++hayooPackageNames               :: SysConfigList -> IOSArrow b String+hayooPackageNames crawlPars     = ( ( readDocument crawlPars hackageStartPage+                                      >>>+                                      getHtmlReferences+                                      >>>+                                      arr getHackagePackage+                                      >>>+                                      isA (not . null)+                                    )+                                  )+                                  >>. (sort >>> nub)++-- ------------------------------------------------------------+
+ src/HayooIndexer.hs view
@@ -0,0 +1,745 @@+{-# OPTIONS #-}++-- ------------------------------------------------------------++module Main (main)+where++import           Codec.Compression.BZip ( compress, decompress )++import           Control.DeepSeq+-- import           Control.Monad+import           Control.Monad.Reader++import qualified Data.Binary                    as B+import           Data.Char+import           Data.Function.Selector+import           Data.Maybe++import           Hayoo.HackagePackage+import           Hayoo.Haddock+import           Hayoo.IndexConfig+import           Hayoo.IndexTypes+import           Hayoo.PackageArchive+import           Hayoo.URIConfig++import           Holumbus.Crawler+import           Holumbus.Crawler.CacheCore+import           Holumbus.Crawler.IndexerCore++import           System.Console.GetOpt+import           System.Environment+import           System.Exit+import           System.IO++import           Text.XML.HXT.Core+import           Text.XML.HXT.Cache+import           Text.XML.HXT.HTTP()+import           Text.XML.HXT.Curl++-- ------------------------------------------------------------++data AppAction+    = BuildIx | UpdatePkg | RemovePkg | BuildCache | MergeIx+      deriving (Eq, Show)++data AppOpts+    = AO+      { ao_progname :: String+      , ao_index    :: String+      , ao_ixout    :: String+      , ao_ixsearch :: String+      , ao_xml      :: String+      , ao_help     :: Bool+      , ao_pkgIndex :: Bool+      , ao_action   :: AppAction+      , ao_defrag   :: Bool+      , ao_partix   :: Bool+      , ao_resume   :: Maybe String+      , ao_packages :: [String]+      , ao_latest   :: Maybe Int+      , ao_getHack  :: Bool+      , ao_pkgRank  :: Bool+      , ao_msg      :: String+      , ao_crawlDoc :: (Int, Int, Int)+      , ao_crawlSav :: Int+      , ao_crawlSfn :: String+      , ao_crawlLog :: (Priority, Priority)+      , ao_crawlPar :: SysConfig+      , ao_crawlFct :: HayooIndexerConfig    -> HayooIndexerConfig+      , ao_crawlPkg :: HayooPkgIndexerConfig -> HayooPkgIndexerConfig+      , ao_crawlCch :: CacheCrawlerConfig    -> CacheCrawlerConfig+      }++-- ------------------------------------------------------------++initAppOpts :: AppOpts+initAppOpts+    = AO+      { ao_progname = "hayooIndexer"+      , ao_index    = ""+      , ao_ixout    = ""+      , ao_ixsearch = ""+      , ao_xml      = ""+      , ao_help     = False+      , ao_pkgIndex = False+      , ao_action   = BuildIx+      , ao_defrag   = False+      , ao_partix   = False+      , ao_resume   = Nothing+      , ao_packages = []+      , ao_latest   = Nothing+      , ao_getHack  = False+      , ao_pkgRank  = False+      , ao_msg      = ""+      , ao_crawlDoc = (25000, 1024, 1)                                          -- max docs, max par docs, max threads: no parallel threads, but 1024 docs are indexed before results are inserted+      , ao_crawlSav = 5000                                                      -- save intervall+      , ao_crawlSfn = "./tmp/ix-"                                               -- save path+      , ao_crawlLog = (DEBUG, NOTICE)                                           -- log cache and hxt+      , ao_crawlPar = withCache' (60 * 60 * 24 * 30)                            -- set cache dir, cache remains valid 1 month, 404 pages are cached+                      >>>+                      withCompression (compress, decompress)                    -- compress cache files+                      >>>+                      withStrictDeserialize yes                                 -- strict input of cache files+                      >>>+                      withAcceptedMimeTypes [ text_html+                                            , application_xhtml+                                            ]+                      >>>+                      withCurl [ (curl_location,             v_1)               -- automatically follow redirects+                               , (curl_max_redirects,        "3")               -- but limit # of redirects to 3+                               ]+                      >>>+                      -- withHTTP [ (curl_max_redirects,        "3") ]          -- nice try: HTTP web access instead of curl, problem: no document size limit+                      -- >>>+                      withRedirect yes+                      >>>+                      withInputOption curl_max_filesize+                                          (show (1024 * 1024 * 3 `div` 2 ::Int)) -- this limit excludes automtically generated pages, sometimes > 1.5 Mbyte+                      >>>+                      withParseHTML no+                      >>>+                      withParseByMimeType yes++      , ao_crawlFct = ( editPackageURIs                                         -- configure URI rewriting+                        >>>+                        disableRobotsTxt                                        -- for hayoo robots.txt is not needed+                      )++      , ao_crawlPkg = disableRobotsTxt++      , ao_crawlCch = ( editPackageURIs                                         -- configure URI rewriting+                        >>>+                        disableRobotsTxt                                        -- for hayoo robots.txt is not needed+                      )+      }+    where+      editPackageURIs+          = chgS theProcessRefs (>>> arr editLatestPackage)++withCache' :: Int -> XIOSysState -> XIOSysState+withCache' sec+    = withCache "./cache" sec yes++-- ------------------------------------------------------------++type HIO = ReaderT AppOpts IO++main :: IO ()+main+    = do pn   <- getProgName+         args <- getArgs+         runReaderT main2 (evalOptions pn args)++-- ------------------------------------------------------------++main2 :: HIO ()+main2+    = do (h, pn) <- asks (ao_help &&& ao_progname)+         if h+            then do msg <- asks ao_msg+                    liftIO $ do hPutStrLn stderr (msg ++ "\n" ++ usageInfo pn hayooOptDescr)+                                if null msg+                                   then exitSuccess+                                   else exitFailure+            else do asks (snd . ao_crawlLog) >>= setLogLevel ""+                    a <- asks ao_action+                    case a of+                      BuildCache -> mainCache+                      MergeIx    -> mainHaddock+                      _          -> do p <- asks ao_pkgIndex+                                       if p+                                          then mainHackage+                                          else mainHaddock+                    liftIO $ exitSuccess++-- ------------------------------------------------------------++mainCache :: HIO ()+mainCache+    = do action+    where+      action+          = asks ao_latest >>=+            maybe action2 updatePackages+      action2+          = do pl <- asks ao_packages+               case pl of+                 [] -> action3+                 _  -> updatePkg pl++      action3+          = do rs <- asks ao_resume+               notice $ if isJust rs+                        then ["resume cache update"]+                        else ["cache hayoo pages"]+               hayooCacher >>= writeResults++      updatePackages latest+          = do notice ["compute list of latest packages"]+               (hk, cp) <- asks (ao_getHack &&& ao_crawlPar)+               pl       <- liftIO $ getNewPackages hk latest+               local (\ opts -> opts { ao_latest   = Nothing+                                     , ao_crawlPar = setDocAge 1 cp    -- force cache update+                                     }+                     ) $ updatePkg pl++      updatePkg []+          = notice ["no packages to be updated"]+      updatePkg ps+          = do notice $ "updating cache with packages:" : ps+               res  <- hayooPackageUpdate ps+               writeResults res+               notice $ "updating cache with latest packages done" : []++-- ------------------------------------------------------------++mainHackage :: HIO ()+mainHackage+    = action+    where+      action+          = do apl <- asks (ao_action &&& ao_packages)+               case apl of+                 (RemovePkg, [])+                     -> noaction+                 (RemovePkg, ps)+                     -> removePkg ps >>= writeRes+                 (UpdatePkg, [])+                     -> noaction+                 (UpdatePkg, ps)+                     -> updatePkg ps >>= writeRes+                 (_,         ps)+                     -> do rs <- asks ao_resume+                           if isJust rs+                              then notice ["resume hackage package description indexing"]+                              else return ()+                           indexPkg ps >>= writeRes++      removePkg :: [String] -> HIO HayooPkgIndexerState+      removePkg ps+          = do notice $ "removing packages from hackage package index:" : ps+               res <- removePackagesPkg+               rnf res `seq`+                   notice $ "packages removed from hackage package index : " : ps+               return res++      updatePkg :: [String] -> HIO HayooPkgIndexerState+      updatePkg ps+          = do notice $ "updating packages from hackage package index:" : ps+               newix <- local (\ opts -> opts { ao_action  = BuildIx+                                              , ao_pkgRank = False+                                              }+                              ) (indexPkg ps)+               oldix <- removePkg ps+               mergePkg newix oldix++      indexPkg :: [String] -> HIO HayooPkgIndexerState+      indexPkg ps+          = do notice $ if null ps+                          then ["indexing all packages from hackage package index"]+                          else "indexing hackage package descriptions for packages:" : ps+               (getS theResultAccu `fmap` hayooPkgIndexer) >>= rankPkg++      rankPkg ix+          = do rank <- asks ao_pkgRank+               if rank+                  then do notice ["computing package ranks"]+                          let res = packageRanking ix+                          rnf res `seq`+                              notice ["package rank computation finished"]+                          return res+                  else do notice ["no package ranks computed"]+                          return ix++mainHaddock :: HIO ()+mainHaddock+    = do action+    where+      action+          = do latest <- asks ao_latest+               maybe action2 updateLatest latest+      action2+          = do apl <- asks (ao_action &&& ao_packages)+               case apl of+                 (RemovePkg, [])+                     -> noaction+                 (RemovePkg, ps)+                     -> removePkg ps >>= writeRes+                 (UpdatePkg, [])+                     -> noaction+                 (UpdatePkg, ps)+                     -> updatePkg ps >>= writeRes+                 (MergeIx, _)+                     -> loadPartialIx >>= mergeAndWritePartialRes+                 (_,         ps)+                     -> do rs <- asks ao_resume+                           if isJust rs+                              then notice ["resume haddock document indexing"]+                              else return ()+                           indexPkg ps >>= writePartialRes+      removePkg ps+          = do notice $ "deleting packages" : ps ++ ["from haddock index"]+               res <- removePackagesIx+               rnf res `seq`+                   notice $ "packages " : ps ++ ["deleted from haddock index"]+               return res++      updatePkg :: [String] -> HIO (HayooIndexerState)+      updatePkg ps+          = do notice $ "updating haddock index with packages:" : ps+               newix <- local (\ opts -> opts { ao_action  = BuildIx+                                              }+                              ) (fst `fmap` indexPkg ps)+               oldix <- removePkg ps+               mergePkg newix oldix++      loadPartialIx :: HIO [Int]+      loadPartialIx+          = local (\ o -> o { ao_action   = BuildIx+                            , ao_packages = []+                            }) (snd `fmap` indexPkg [])++      indexPkg :: [String] -> HIO (HayooIndexerState, [Int])+      indexPkg ps+          = do notice $ if null ps+                          then ["indexing all haddock pages"]+                          else "indexing haddock for packages:" : ps+               (getS (theResultAccu .&&&. theListOfDocsSaved) `fmap` hayooIndexer)++      updateLatest latest+          = do notice ["reindex with latest packages"]+               hk  <- asks ao_getHack+               ps  <- liftIO $ getNewPackages hk latest+               if null ps+                  then notice ["no new packages to be indexed"]+                  else do res <- local (\ o -> o { ao_latest = Nothing }+                                       ) $ updatePkg ps+                          notice ["reindex with latest packages finished"]+                          writeRes res++-- ------------------------------------------------------------++noaction :: HIO ()+noaction+    = notice ["no packages to be processed"]++-- ------------------------------------------------------------++removePacks :: (B.Binary di, NFData di) =>+                   (Document di -> String) -> HIO (HolumbusState di)+removePacks getPkgName'+    = do (ix, (pkg, dfg)) <- asks (ao_index &&& ao_packages &&& ao_defrag)+         liftIO $ removePackages' getPkgName' ix pkg dfg++removePackagesIx ::HIO HayooIndexerState +removePackagesIx+    = removePacks getPkgNameFct++removePackagesPkg :: HIO HayooPkgIndexerState +removePackagesPkg+    = removePacks getPkgNamePkg++-- ------------------------------------------------------------++mergePkg :: (B.Binary a) => HolumbusState a -> HolumbusState a -> HIO (HolumbusState a)+mergePkg nix oix+    = do notice $ ["merging existing index with new packages"]+         liftIO $ unionIndexerStatesM oix nix++-- ------------------------------------------------------------++writePartialRes :: (HayooIndexerState, [Int]) -> HIO ()+writePartialRes (x, ps)+    = do part <- asks ao_partix+         if part+            then mergeAndWritePartialRes ps+            else writeRes x++mergeAndWritePartialRes :: [Int] -> HIO ()+mergeAndWritePartialRes ps+    = do pxs <- (\ fn -> map (mkTmpFile 10 fn) ps) `fmap` asks ao_crawlSfn+         out <- asks ao_ixsearch+         mergeAndWritePartialRes' id' pxs out+    where+      id' :: SmallDocuments FunctionInfo -> SmallDocuments FunctionInfo+      id' = id++-- ------------------------------------------------------------++writeRes :: (XmlPickler a, B.Binary a) => HolumbusState a -> HIO ()+writeRes x+    = writeSearchBin' x >> writeResults x+    where+      writeSearchBin' s+          = do out <- asks ao_ixsearch+               writeSearchBin out s++writeResults :: (XmlPickler a, B.Binary a) => a -> HIO ()+writeResults v+    = do (xf, of') <- asks (ao_xml &&& (ao_ixout &&& ao_index))+         writeXml xf  v+         writeBin (out of') v+    where+      out (bf, bi)+          | null bf     = bi+          | otherwise   = bf+      +-- ------------------------------------------------------------++hayooCacher :: HIO CacheCrawlerState+hayooCacher+    = do o <- ask+         liftIO $ stdCacher+                    (ao_crawlDoc o)+                    (ao_crawlSav o, ao_crawlSfn o)+                    (ao_crawlLog o)+                    (ao_crawlPar o)+                    (ao_crawlCch o)+                    (ao_resume o)+                    hayooStart+                    (hayooRefs True [])++-- ------------------------------------------------------------++hayooPackageUpdate :: [String] -> HIO CacheCrawlerState+hayooPackageUpdate pkgs+    = do o <- ask+         liftIO $ stdCacher+                    (ao_crawlDoc o)+                    (ao_crawlSav o, ao_crawlSfn o)+                    (ao_crawlLog o)+                    (ao_crawlPar o)+                    -- (setDocAge 1 (ao_crawlPar o))              -- cache validation initiated (1 sec valid) +                    (ao_crawlCch o)+                    Nothing+                    hayooStart+                    (hayooRefs True pkgs)++-- ------------------------------------------------------------++hayooPkgIndexer :: HIO HayooPkgIndexerCrawlerState+hayooPkgIndexer+    = do o <- ask+         liftIO $ stdIndexer+                    (config o)+                    (ao_resume o)+                    hackageStart+                    emptyHolumbusState+    where+    config0 o+        = indexCrawlerConfig+          (ao_crawlPar o)+          (hayooRefs False $ ao_packages o)+          Nothing+          (Just $ checkDocumentStatus >>> preparePkg)+          (Just $ hayooGetPkgTitle)+          (Just $ hayooGetPkgInfo)+          hayooPkgIndexContextConfig++    config o+        = ao_crawlPkg o $+          setCrawlerTraceLevel ct ht   $+          setCrawlerSaveConf si sp     $+          setCrawlerMaxDocs md mp mt   $+          config0                      $ o+        where+          (ct, ht)      = ao_crawlLog o+          si            = ao_crawlSav o+          sp            = ao_crawlSfn o+          (md, mp, mt)  = ao_crawlDoc o++-- ------------------------------------------------------------++hayooIndexer :: HIO HayooIndexerCrawlerState+hayooIndexer+    = do o <- ask+         liftIO $ stdIndexer+                    (config o)+                    (ao_resume o)+                    hayooStart+                    emptyHolumbusState+    where+    config0 o+        = indexCrawlerConfig+          (ao_crawlPar o)+          (hayooRefs True $ ao_packages o)+          Nothing+          (Just $ checkDocumentStatus >>> prepareHaddock)+          (Just $ hayooGetTitle)+          (Just $ hayooGetFctInfo)+          hayooIndexContextConfig++    config o+        = ao_crawlFct o $+          setCrawlerTraceLevel ct ht $+          setCrawlerSaveConf si sp   $+          setCrawlerSaveAction partA $+          setCrawlerMaxDocs md mp mt $+                                  -- haddock pages don't need to be scanned for new URIs+          setCrawlerPreRefsFilter noHaddockPage $+          config0                    $ o+        where+          xout              = ao_xml      o+          (ct, ht)          = ao_crawlLog o+          si                = ao_crawlSav o+          sp                = ao_crawlSfn o+          (md, mp, mt)      = ao_crawlDoc o+          partA+              | ao_partix o = writePartialIndex (not . null $ xout)+              | otherwise   = const $ return ()++noHaddockPage :: IOSArrow XmlTree XmlTree+noHaddockPage+    = fromLA $+      hasAttrValue transferURI (not . isHaddockURI) `guards` this++-- ------------------------------------------------------------++notice :: MonadIO m => [String] -> m ()+notice = noticeC "hayoo"++-- ------------------------------------------------------------++evalOptions :: String -> [String] -> AppOpts+evalOptions pn args+    = foldl (.) (ef1 . ef2) opts $ initAppOpts { ao_progname = pn }+    where+    (opts, ns, es)   = getOpt Permute hayooOptDescr args+    ef1+        | null es    = id+        | otherwise  = \ x -> x { ao_help   = True+                                , ao_msg = concat es+                                }+        | otherwise  = id+    ef2+        | null ns    = id+        | otherwise  = \ x -> x { ao_help   = True+                                , ao_msg = "wrong program arguments: " ++ unwords ns+                                }++-- ------------------------------------------------------------++hayooOptDescr :: [OptDescr (AppOpts -> AppOpts)]+hayooOptDescr+    = [ Option "h?" ["help"]+        ( NoArg $+          \ x -> x { ao_help     = True }+        )+        "usage info"++      , Option "" ["fct-index"]+        ( NoArg $+          \ x -> x { ao_pkgIndex = False+                   , ao_crawlSfn = "./tmp/ix-"+                   }+        )+        "process index for haddock functions and types (default)"++      , Option "" ["pkg-index"]+        ( NoArg $+          \ x -> x { ao_pkgIndex = True+                   , ao_crawlSfn = "./tmp/pkg-"+                   }+        )+        "process index for hackage package description pages"++      , Option "" ["cache"]+        ( NoArg $+          \ x -> x { ao_action   = BuildCache }+        )+        "update the cache"+                   +      , Option "i" ["index"]+        ( ReqArg+          (\ f x -> x { ao_index = f })+          "INDEX"+        )+        "index input file (binary format) to be operated on"++      , Option "n" ["new-index"]+        ( ReqArg+          (\ f x -> x { ao_ixout = f })+          "NEW-INDEX"+        )+        "new index file (binary format) to be generatet, default is index file"++      , Option "s" ["new-search"]+        ( ReqArg+          (\ f x -> x { ao_ixsearch = f })+          "SEARCH-INDEX"+        )+        "new search index files (binary format) ready to be used by Hayoo! search"++      , Option "x" ["xml-output"]+        ( ReqArg+          (\ f x -> x { ao_xml = f })+          "XML-FILE"+        )+        "output of final crawler state in xml format, \"-\" for stdout"++      , Option "r" ["resume"]+        ( ReqArg (\ s x -> x { ao_resume = Just s})+          "FILE"+        )+        "resume program with file containing saved intermediate state"+                   +      , Option "p" ["packages"]+        ( ReqArg+          (\ l x -> x { ao_packages = pkgList l })+          "PACKAGE-LIST"+        )+        "packages to be processed, a comma separated list of package names"++      , Option "u" ["update"]+        ( NoArg $+          \ x -> x { ao_action   = UpdatePkg }+        )+        "update packages specified by \"packages\" option"++      , Option "d" ["delete"]+        ( NoArg $+          \ x -> x { ao_action   = RemovePkg }+        )+        "delete packages specified by \"packages\" option"++      , Option "" ["maxdocs"]+        ( ReqArg (setOption parseInt (\ x i -> x { ao_crawlDoc = setMaxDocs i $+                                                                 ao_crawlDoc x }))+          "NUMBER"+        )+        "maximum # of docs to be processed"++      , Option "" ["maxthreads"]+        ( ReqArg (setOption parseInt (\ x i -> x { ao_crawlDoc = setMaxThreads i $+                                                                 ao_crawlDoc x }))+          "NUMBER"+        )+        ( "maximum # of parallel threads, 0: sequential, 1: single thread with binary merge," +++          " else real parallel threads, default: 1" )++      , Option "" ["maxpar"]+        ( ReqArg (setOption parseInt (\ x i -> x { ao_crawlDoc = setMaxParDocs i $+                                                                 ao_crawlDoc x }))+          "NUMBER"+        )+        "maximum # of docs indexed at once before the results are inserted into index, default: 1024"++      , Option "" ["valid"]+        ( ReqArg (setOption parseTime (\ x t -> x { ao_crawlPar = setDocAge t $+                                                                  ao_crawlPar x }))+          "DURATION"+        )+        ( "validate cache for pages older than given time, format: " +++          "10sec, 5min, 20hours, 3days, 5weeks, 1month, default is 1month" )++      , Option "" ["latest"]+        ( ReqArg (setOption parseTime (\ x t -> x { ao_latest   = Just t }))+          "DURATION"+        )+        "select latest packages newer than given time, format like in option \"valid\""++      , Option "" ["partition"]+        ( ReqArg (setOption parseInt (\ x i -> x { ao_partix    = True+                                                 , ao_crawlSav  = i }))+          "NUMBER"+        )+        "partition the index into smaller chunks of given # of docs and write the index part by part"++      , Option "" ["merge"]+        ( ReqArg (\ s x -> x { ao_action = MergeIx+                             , ao_resume = Just s })+          "FILE"+        )+        "merge chunks into final index, resume with latest crawler state"++      , Option "" ["save"]+        ( ReqArg (setOption parseInt (\ x i -> x { ao_crawlSav  = i }))+          "NUMBER"+        )+        "save intermediate results of index, default is 5000"++      , Option "" ["defragment"]+        ( NoArg $+          \ x -> x { ao_defrag    = True }+        )+        "defragment index after delete or update"++      , Option "" ["hackage"]+        ( NoArg $+          \ x -> x { ao_getHack   = True }+        )+        "when processing latest packages, first update the package list from hackage"++      , Option "" ["ranking"]+        ( NoArg $+          \   x -> x { ao_pkgRank   = True }+        )+        "when processing package index, compute package rank, default is no rank"+                   +      ]+    where+    pkgList+        = words . map (\ x -> if x == ',' then ' ' else x)++    setOption parse f s x+        = either (\ e -> x { ao_msg  = e+                           , ao_help = True+                           }+                 ) (f x) . parse $ s++-- ------------------------------------------------------------++parseInt                                :: String -> Either String Int+parseInt s+    | match "[0-9]+" s                  = Right $ read s+    | otherwise                         = Left  $ "number expected in option arg"++parseTime                               :: String -> Either String Int+parseTime s+    | match "[0-9]+(s(ec)?)?"      s    = Right $ t+    | match "[0-9]+(m(in)?)?"      s    = Right $ t * 60+    | match "[0-9]+(h(our(s)?)?)?" s    = Right $ t * 60 * 60+    | match "[0-9]+(d(ay(s)?)?)?"  s    = Right $ t * 60 * 60 * 24+    | match "[0-9]+(w(eek(s)?)?)?" s    = Right $ t * 60 * 60 * 24 * 7+    | match "[0-9]+(m(onth(s)?)?)?" s   = Right $ t * 60 * 60 * 24 * 30+    | match "[0-9]+(y(ear(s)?)?)?" s    = Right $ t * 60 * 60 * 24 * 30 * 365+    | otherwise                         = Left  $ "error in duration format in option arg"+    where+    t                                   = read . filter isDigit $ s++-- ------------------------------------------------------------++setMaxDocs                              :: Int -> (Int, Int, Int) -> (Int, Int, Int)+setMaxDocs    md (_md, mp, mt)          = (md, md `min` mp, mt)++setMaxParDocs                           :: Int -> (Int, Int, Int) -> (Int, Int, Int)+setMaxParDocs mp (md, _mp, mt)          = (md, mp, mt)++setMaxThreads                           :: Int -> (Int, Int, Int) -> (Int, Int, Int)+setMaxThreads mt (md, mp, _mt)          = (md, mp, mt)++setDocAge                               :: Int -> SysConfig -> SysConfig+setDocAge d                             = (>>> withCache' d)++-- ------------------------------------------------------------
+ src/HayooSearch.hs view
@@ -0,0 +1,37 @@+  -- ----------------------------------------------------------------------------++{- |+  Module     : Main+  Copyright  : Copyright (C) 2011 Timo B. Huebel+  License    : MIT++  Maintainer : Timo B. Huebel (tbh@holumbus.org)+  Stability  : experimental+  Portability: portable+  Version    : 0.1++  Main program to use as standalone webserver for serving Hayoo!++-}++-- ----------------------------------------------------------------------------++module Main where++import Hayoo.Search.Application++import Hack.Handler.SimpleServer++-- ----------------------------------------------------------------------------++-- | Maybe read these from the command line ... somewhen+ixBase :: FilePath+ixBase          = "./lib"++-- | The main application, fire up the server here!+main :: IO ()+main = do+       apl <- hayooInit ixBase+       run 4242 $ apl++-- ----------------------------------------------------------------------------
+ src/HayooSnap.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE TemplateHaskell #-}++{-|++This is the entry point for this web server application.  It supports+easily switching between interpreting source and running statically+compiled code.++In either mode, the generated program should be run from the root of+the project tree.  When it is run, it locates its templates, static+content, and source files in development mode, relative to the current+working directory.++When compiled with the development flag, only changes to the+libraries, your cabal file, or this file should require a recompile to+be picked up.  Everything else is interpreted at runtime.  There are a+few consequences of this.++First, this is much slower.  Running the interpreter takes a+significant chunk of time (a couple tenths of a second on the author's+machine, at this time), regardless of the simplicity of the loaded+code.  In order to recompile and re-load server state as infrequently+as possible, the source directories are watched for updates, as are+any extra directories specified below.++Second, the generated server binary is MUCH larger, since it links in+the GHC API (via the hint library).++Third, and the reason you would ever want to actually compile with+development mode, is that it enables a faster development cycle. You+can simply edit a file, save your changes, and hit reload to see your+changes reflected immediately.++When this is compiled without the development flag, all the actions+are statically compiled in.  This results in faster execution, a+smaller binary size, and having to recompile the server for any code+change.++-}++module Main where++#ifdef DEVELOPMENT+import           Snap.Extension.Loader.Devel+import           Snap.Http.Server (quickHttpServe)+#else+import           Snap.Extension.Server+#endif++import           Hayoo.Snap.Application+import           Hayoo.Snap.Site++main :: IO ()+#ifdef DEVELOPMENT+main = do+    -- All source directories will be watched for updates+    -- automatically.  If any extra directories should be watched for+    -- updates, include them here.+    snap <- $(let extraWatcheDirs = ["resources/templates"]+              in loadSnapTH 'applicationInitializer 'site extraWatcheDirs)+    quickHttpServe snap+#else+main = quickHttpServe applicationInitializer site+#endif