-------------------------------------------------------------------------------- -- Title: WikiSearchService.lua -- Description: Like a square peg in a round hole -- Author: Raphaël Szwarc http://alt.textdrive.com/lua/ -- Creation Date: January 30, 2007 -- Legal: Copyright (C) 2007 Raphaël Szwarc -- Under the terms of the MIT License -- http://www.opensource.org/licenses/mit-license.html -------------------------------------------------------------------------------- -- import dependencies local HTTP = require( 'HTTP' ) local Template = require( 'Template' ) local URL = require( 'URL' ) local WikiFinder = require( 'WikiFinder' ) local WikiIndex = require( 'WikiIndex' ) local WikiSearch = require( 'WikiSearch' ) local WikiService = require( 'WikiService' ) local BaseLink = WikiService.BaseLink local DateLink = WikiService.DateLink local FeedLink = WikiService.FeedLink local IndexLink = WikiService.IndexLink local ContentIterator = WikiService.ContentIterator local NameIterator = WikiService.NameIterator local Description = WikiService.Description local Encode = WikiService.Encode local GetType = WikiService.GetType local HTML = WikiService.HTML local Path = WikiService.Path local Tag = WikiService.Tag local getmetatable = getmetatable local next = next local pairs = pairs local require = require local setmetatable = setmetatable local table = table local tostring = tostring -------------------------------------------------------------------------------- -- WikiSearchService -------------------------------------------------------------------------------- module( 'WikiSearchService' ) _VERSION = '1.0' local self = setmetatable( _M, {} ) local meta = getmetatable( self ) -------------------------------------------------------------------------------- -- Utilities -------------------------------------------------------------------------------- local function Hit( aTemplate, anIterator, someHits ) local hasData = false for aContent, aURL, anExtract in anIterator do if not someHits[ aContent.name ] then local aNameTemplate = aTemplate[ 'names' ] if someHits.n == 999 then someHits.m = true break end aNameTemplate[ 'href' ] = Encode( aURL.path ) aNameTemplate[ 'name' ] = Encode( aContent.title ) aNameTemplate[ 'tag' ] = Tag( aContent.modification, true ) aNameTemplate[ 'extract' ] = anExtract aTemplate[ 'names' ] = aNameTemplate someHits[ aContent.name ] = true someHits.n = ( someHits.n or 0 ) + 1 hasData = true end end if not hasData then aTemplate = nil end return aTemplate end local function DirectSearch( aValue ) local NaturalComparator = require( 'NaturalComparator' ) local WikiContent = require( 'WikiContent' ) local aContent = WikiContent( aValue ) local aMap = {} local aList = {} if aContent and aContent.exists then aMap[ aContent.name ] = true end for aToken in ( aValue or '' ):gsub( '%p', ' ' ):gmatch( '([%S]+)' ) do local aContent = WikiContent( aToken ) if aContent and aContent.exists then aMap[ aContent.name ] = true end end for aName, _ in pairs( aMap ) do aList[ #aList + 1 ] = aName end table.sort( aList, NaturalComparator() ) return NameIterator( aList ) end local function DirectHit( aTemplate, aQuery, someHits ) local anIterator = DirectSearch( aQuery ) return Hit( aTemplate, anIterator, someHits ) end local function TitleSearch( aQuery ) if aQuery then return ContentIterator( WikiSearch[ aQuery ] ) end return ContentIterator( WikiIndex[ '' ] ) end local function TitleHit( aTemplate, aQuery, someHits ) local anIterator, aCount, hasMore = TitleSearch( aQuery ) someHits.m = hasMore return Hit( aTemplate, anIterator, someHits ) end local function ContentSearch( aQuery ) local anIterator = WikiFinder[ aQuery ] local aContentIterator = function() local HTTPExtra = require( 'HTTPExtra' ) local HTTPService = require( 'HTTPService' ) local WikiContent = require( 'WikiContent' ) local WikiContentService = require( 'WikiContentService' ) local aName, anExtract = anIterator() if aName then local aContent = WikiContent( aName ) local aService = WikiContentService( aContent ) local aURL = HTTPService[ aService ] return aContent, aURL, anExtract end end return aContentIterator end local function ContentHit( aTemplate, aQuery, someHits ) local anIterator = ContentSearch( aQuery ) return Hit( aTemplate, anIterator, someHits ) end local function Search( aQuery ) local aTemplate = Template[ 'WikiSearchService.txt' ] local someHits = { n = 0, m = false } local aKey = nil local anIterator = function() aKey = next( someHits, aKey ) if aKey ~= nil then return aKey end end DirectHit( aTemplate[ 'directHit' ], aQuery, someHits ) TitleHit( aTemplate[ 'titleHit' ], aQuery, someHits ) ContentHit( aTemplate[ 'contentHit' ], aQuery, someHits ) return ContentIterator( anIterator ) end -------------------------------------------------------------------------------- -- Service methods -------------------------------------------------------------------------------- self.toURL =function( aService, anObject ) return URL( aService.prefix ) end self.toObject = function( aService, aURL ) local WikiSearchService = require( 'WikiSearchService' ) local aQuery = HTTP.request.parameter[ 'q' ] local aService = WikiSearchService( aQuery ) return aService end function self:getHtml() local aLayoutTemplate = Template[ 'WikiLayout.txt' ] local aTemplate = Template[ 'WikiSearchService.txt' ] local aQuery = self.query local someHits = { n = 0, m = false } local aTitle = ( '“%s” — Search' ):format( aQuery or '' ) aTemplate[ 'directHit' ] = DirectHit( aTemplate[ 'directHit' ], aQuery, someHits ) aTemplate[ 'titleHit' ] = TitleHit( aTemplate[ 'titleHit' ], aQuery, someHits ) aTemplate[ 'contentHit' ] = ContentHit( aTemplate[ 'contentHit' ], aQuery, someHits ) aTemplate[ 'status' ] = nil if someHits.n == 0 then local aStatusTemplate = Template[ 'WikiSearchStatus.txt' ] aStatusTemplate[ 'query' ] = Encode( aQuery ) aTemplate[ 'status' ] = aStatusTemplate HTTP.response.status.code = 404 HTTP.response.status.description = 'Not Found' end aTemplate[ 'description' ] = Encode( Description( someHits.n, someHits.m ) ) aTemplate[ 'title' ] = Encode( aTitle ) aLayoutTemplate[ 'baseLink' ] = Encode( BaseLink() ) aLayoutTemplate[ 'indexLink' ] = Encode( IndexLink( aQuery ) ) aLayoutTemplate[ 'dateLink' ] = Encode( DateLink() ) aLayoutTemplate[ 'feedLink' ] = FeedLink( self, ( someHits.n ) ) aLayoutTemplate[ 'path' ] = Path( self ) aLayoutTemplate[ 'query' ] = Encode( aQuery ) aLayoutTemplate[ 'robot' ] = 'noindex' aLayoutTemplate[ 'title' ] = Encode( aTitle ) aLayoutTemplate[ 'content' ] = aTemplate return tostring( aLayoutTemplate ) end function self:getXml() local WikiFeed = require( 'WikiFeed' ) local WikiIndex = require( 'WikiIndex' ) local anIterator = Search( self.query ) local aGenerator = HTML local aQuery = self.query or '' local aTitle = ( '“%s” — Search' ):format( aQuery ) local aContext = { title = aTitle, link = HTTP.request.url, creation = WikiIndex[ 'creation' ] } HTTP.response.header[ 'content-type' ] = 'application/atom+xml; charset=utf-8' return tostring( WikiFeed( anIterator, aGenerator, aContext ) ) end function self:get( aType ) return GetType( self, aType ) end -------------------------------------------------------------------------------- -- Metamethods -------------------------------------------------------------------------------- function meta:__call( aQuery ) local aService = { query = aQuery } setmetatable( aService, self ) self.__index = self return aService end function meta:__concat( aValue ) return tostring( self ) .. tostring( aValue ) end function meta:__tostring() return ( '%s/%s' ):format( self._NAME, self._VERSION ) end function self:__concat( aValue ) return tostring( self ) .. tostring( aValue ) end function self:__tostring() return 'Search' end