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 +181/−0
- LICENSE +21/−0
- README +19/−0
- Setup.lhs +8/−0
- index/hayoo/content.js +123/−0
- index/hayoo/favicon.ico binary
- index/hayoo/fhw.gif binary
- index/hayoo/ft.png binary
- index/hayoo/hayoo.css +491/−0
- index/hayoo/hayoo.js +136/−0
- index/hayoo/hayoo.png binary
- index/hayoo/hol.png binary
- index/hayoo/holumbus.png binary
- index/hayoo/loader.gif binary
- index/hayoo/minus.png binary
- index/hayoo/opensearch.xml +10/−0
- index/hayoo/plus.png binary
- index/hayoo/prototype.js +4221/−0
- src/Hayoo/FunctionInfo.hs +62/−0
- src/Hayoo/HackagePackage.hs +153/−0
- src/Hayoo/Haddock.hs +872/−0
- src/Hayoo/IndexConfig.hs +198/−0
- src/Hayoo/IndexTypes.hs +163/−0
- src/Hayoo/PackageArchive.hs +74/−0
- src/Hayoo/PackageInfo.hs +104/−0
- src/Hayoo/PackageRank.hs +126/−0
- src/Hayoo/Search/Application.hs +172/−0
- src/Hayoo/Search/Common.hs +38/−0
- src/Hayoo/Search/EvalSearch.hs +334/−0
- src/Hayoo/Search/HTML.hs +243/−0
- src/Hayoo/Search/JSON.hs +98/−0
- src/Hayoo/Search/Pages/Static.hs +268/−0
- src/Hayoo/Search/Pages/Template.hs +136/−0
- src/Hayoo/Search/Parser.hs +208/−0
- src/Hayoo/Signature.hs +96/−0
- src/Hayoo/Snap/Application.hs +54/−0
- src/Hayoo/Snap/Extension/HayooState.hs +132/−0
- src/Hayoo/Snap/Site.hs +173/−0
- src/Hayoo/URIConfig.hs +147/−0
- src/HayooIndexer.hs +745/−0
- src/HayooSearch.hs +37/−0
- src/HayooSnap.hs +65/−0
@@ -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++-- ------------------------------------------------------------
@@ -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.
@@ -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.
@@ -0,0 +1,8 @@+#!/usr/bin/runhaskell++> module Main where++> import Distribution.Simple++> main :: IO ()+> main = defaultMain
@@ -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 "->" 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". \+ <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);+}
binary file changed (absent → 1150 bytes)
binary file changed (absent → 3744 bytes)
binary file changed (absent → 9440 bytes)
@@ -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;+}++
@@ -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\"> </div><div id=\"documents\"> </div><div id=\"pager\"> </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);+}
binary file changed (absent → 9735 bytes)
binary file changed (absent → 11692 bytes)
binary file changed (absent → 6230 bytes)
binary file changed (absent → 1849 bytes)
binary file changed (absent → 178 bytes)
@@ -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>
binary file changed (absent → 185 bytes)
@@ -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,'&').replace(/</g,'<').replace(/>/g,'>');+ },+ unescapeHTML: function() {+ return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/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();
@@ -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++-- ------------------------------------------------------------
@@ -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"+ )+ )++-- ------------------------------------------------------------
@@ -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: "http://www.w3.org/XML/1998/namespace"</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+++-- -}++-- ------------------------------------------------------------
@@ -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]++-- -----------------------------------------------------------------------------
@@ -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++-- ------------------------------------------------------------
@@ -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)+++-- ------------------------------------------------------------
@@ -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++-- ------------------------------------------------------------
@@ -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,[])+ ]+-- -}+-- ------------------------------------------------------------
@@ -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+ )++-- ------------------------------------------------------------
@@ -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)]+ )++-- ----------------------------------------------------------------------------
@@ -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++-- ----------------------------------------------------------------------------
@@ -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+
@@ -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+
@@ -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('','map'); return false;", A.href "hayoo.html?query=map&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('','name:map'); return false;", A.href "hayoo.html?query=name%3Amap&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('','map OR fold'); return false;", A.href "hayoo.html?query=map%20OR%20fold&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('','map package:containers'); return false;", A.href "hayoo.html?query=map%20package%3Acontainers&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('','map hierarchy:Lazy'); return false;", A.href "hayoo.html?query=map%20hierarchy%3ALazy&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('','(map OR fold) module:Data.Map'); return false;", A.href "hayoo.html?query=(map%20OR%20fold)%20module%3AData.Map&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('','name:attr module:Text.XML'); return false;", A.href "hayoo.html?query=name%3Aattr%20module%3AText.XML&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+
@@ -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"] " "+ script' "text/javascript" [A.src "hayoo/hayoo.js"] " "++ -- 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++
@@ -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+
@@ -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 ++ ")?\\)"++-- ------------------------------------------------------------
@@ -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++------------------------------------------------------------------------------
@@ -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++------------------------------------------------------------------------------++
@@ -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++------------------------------------------------------------------------------
@@ -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)++-- ------------------------------------------------------------+
@@ -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)++-- ------------------------------------------------------------
@@ -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++-- ----------------------------------------------------------------------------
@@ -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