-------------------------------------------------------------------------------- -- Title: WikiService.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 os = require( 'os' ) local table = require( 'table' ) local getmetatable = getmetatable local next = next local pairs = pairs local require = require local select = select local setmetatable = setmetatable local tonumber = tonumber local tostring = tostring local type = type -------------------------------------------------------------------------------- -- WikiService -------------------------------------------------------------------------------- module( 'WikiService' ) _VERSION = '1.0' local self = setmetatable( _M, {} ) local meta = getmetatable( self ) -------------------------------------------------------------------------------- -- Utilities -------------------------------------------------------------------------------- function Capitalize( aValue ) return ( tostring( aValue or '' ):lower():gsub( '(%l)([%w_\']*)', function( first, second ) return first:upper() .. second end ) ) end function Encode( aValue ) local anEncoder = function( aValue ) return ( '&#%02d;' ):format( aValue:byte() ) end return ( tostring( aValue or '' ):gsub( '([<>&\'"])', anEncoder ) ) end function Trim( aValue ) return ( tostring( aValue or '' ):gsub( '^[%c%s]+', '' ):gsub( '[%s%c]+$', '' ) ) end -------------------------------------------------------------------------------- -- Name utilities -------------------------------------------------------------------------------- function Names( anIterator ) local NaturalComparator = require( 'NaturalComparator' ) local WikiContent = require( 'WikiContent' ) local aMap = {} local aList = {} local anIndex = 1 local hasMore = false for aName in anIterator do aMap[ aName ] = true anIndex = anIndex + 1 if anIndex == 1000 then hasMore = true break end end anIndex = 1 for aName, aValue in pairs( aMap ) do aList[ anIndex ] = aName anIndex = anIndex + 1 end table.sort( aList, NaturalComparator() ) return aList, hasMore end function NameIterator( someNames ) local HTTPExtra = require( 'HTTPExtra' ) local HTTPService = require( 'HTTPService' ) local WikiContent = require( 'WikiContent' ) local WikiContentService = require( 'WikiContentService' ) local aCount = #someNames local anIndex = 1 return function() if anIndex <= aCount then local aName = someNames[ anIndex ] local aContent = WikiContent( aName ) local aService = WikiContentService( aContent ) local aURL = HTTPService[ aService ] anIndex = anIndex + 1 return aContent, aURL end end end function ContentIterator( anIterator ) local someNames, hasMore = Names( anIterator ) return NameIterator( someNames ), #someNames, hasMore end function Description( aCount, hasMore ) aCount = aCount or 0 if aCount == 0 then return 'no item' elseif aCount == 1 then return '1 item' else local aDescription = ( '%d items' ):format( aCount ) if hasMore then aDescription = 'over ' .. aDescription end return aDescription end end -------------------------------------------------------------------------------- -- Format utilities -------------------------------------------------------------------------------- local suffixes = { 'st', 'nd', 'rd' } local function Suffix( aNumber ) local aSuffix = 'th' if aNumber ~= 11 and aNumber ~= 12 and aNumber ~= 13 then if aNumber > 9 then aNumber = aNumber % 10 end aSuffix = suffixes[ aNumber ] or aSuffix end return aSuffix end local function FormatDay( aDay ) local aDay = tonumber( aDay ) local aSuffix = Suffix( aDay ) return ' ' .. aDay .. aSuffix .. ' ' end function FormatDate( aTime ) return os.date( '!%A, %B %d %Y', aTime ):gsub( '%s(%d%d)%s', FormatDay ) end function FormatTime( aTime ) return os.date( '!%I:%M %p', aTime ):gsub( '^0(%d)', '%1' ) end function FormatDateTime( aTime ) return ( '%s at %s' ):format( FormatDate( aTime), FormatTime( aTime ) ) end function Today( aModification ) local aModification = aModification or 0 local aDate = os.date( '!*t' ) local aStartDate = { year = aDate.year, month = aDate.month, day = aDate.day, hour = 0, min = 0, sec = 0, isdst = aDate.isdst } local aStartTime = os.time( aStartDate ) local anEndDate = { year = aDate.year, month = aDate.month, day = aDate.day, hour = 23, min = 59, sec = 59, isdst = aDate.isdst } local anEndTime = os.time( anEndDate ) local isToday = aModification >= aStartTime and aModification <= anEndTime return isToday, aStartTime, anEndTime end function Yesterday( aModification ) local aModification = aModification or 0 local _, aStartTime, anEndTime = Today( aModification ) local aStartTime = aStartTime - 86400 local anEndTime = anEndTime - 86400 local isYesterday = aModification >= aStartTime and aModification <= anEndTime return isYesterday, aStartTime, anEndTime end function ThisWeek( aModification ) local aModification = aModification or 0 local _, aStartTime, anEndTime = Today( aModification ) local aStartTime = aStartTime - ( 7 * 86400 ) local isThisWeek = aModification >= aStartTime and aModification <= anEndTime return isThisWeek, aStartTime, anEndTime end -------------------------------------------------------------------------------- -- Content utilities -------------------------------------------------------------------------------- function BaseLink() return HTTP.request.url + '/' end function FeedLink( aService, aCount ) if aService and aCount and aCount > 0 then local aQuery = HTTP.request.url.query local aLink = tostring( aService.path ) if not aLink:find( '%.xml$' ) then aLink = aLink .. '.xml' end aLink = Encode( aLink ) if aQuery and next( aQuery ) then aLink = aLink .. '?' .. aQuery end return ( '' ):format( aLink ) end end function IndexLink( aPrefix ) local HTTPExtra = require( 'HTTPExtra' ) local HTTPService = require( 'HTTPService' ) local WikiIndexService = require( 'WikiIndexService' ) local aFirst, aSecond = ( aPrefix or '' ):match( '^(%w)(%w?)' ) local aService = WikiIndexService( aFirst, aSecond ) local aURL = HTTPService[ aService ] return aURL.path end function DateLink( aYear, aMonth, aDay ) local HTTPExtra = require( 'HTTPExtra' ) local HTTPService = require( 'HTTPService' ) local WikiDateService = require( 'WikiDateService' ) local aService = WikiDateService( aYear, aMonth, aDay ) local aURL = HTTPService[ aService ] return aURL.path end function Path( aService, ... ) local WikiPath = require( 'WikiPath' ) local aPath = WikiPath() local aList = {} aList[ #aList + 1 ] = { href = aService.path, title = tostring( aService ) } aService = aService.parent for anIndex = 1, select( '#', ... ) do local aService = select( anIndex, ... ) if aService then aList[ #aList + 1 ] = { href = aService.path, title = tostring( aService ) } end end while aService do aList[ #aList + 1 ] = { href = aService.path, title = tostring( aService ) } aService = aService.parent end for anIndex = #aList, 1, -1 do aPath[ #aPath + 1 ] = aList[ anIndex ] end return aPath end function Render( aText ) if aText then local markdown = require( 'markdown' ) aText = aText:gsub( '(`?)(<.->)(`?)', '`%2`' ) aText = markdown( aText ) aText = aText:gsub( '(`)(<.->)(`)', '%2' ) return aText end end function HTML( aContent ) local Cache = require( 'Cache' ) local File = require( 'File' ) local aTime = aContent.modification local aPath = aContent.directory.path local aFile = File( aPath, 'data.html' ) local aContent = function() return Render( aContent.text ) end return Cache( aFile.path, aContent, aTime ) end function Text( aContent ) local aText = HTML( aContent ) if aText then aText = aText:gsub( '(<.->)', '' ) end return aText end function GetType( self, aType ) local aMethod = 'get' .. Capitalize( aType or 'html' ) if type( self[ aMethod ] ) == 'function' then return self[ aMethod ]( self ) end end function Tag( aModification, isSmall ) local aDay = os.date( '!%A', aModification ) local anHour = tonumber( os.date( '!%I', aModification ) ) local aMeridian = os.date( '!%p', aModification ) local aTag = nil local aLabel = nil if Today( aModification ) then aLabel = ( 'Edited today around %d %s' ):format( anHour, aMeridian ) aTag = 1 elseif Yesterday( aModification ) then aLabel = ( 'Edited yesterday around %d %s' ):format( anHour, aMeridian ) aTag = 2 elseif ThisWeek( aModification ) then aLabel = ( 'Edited last %s around %d %s' ):format( aDay, anHour, aMeridian ) aTag = 3 end if aTag and aLabel then local aType = 'tag' if isSmall then aType = 'bullet' end return ( '\'tag\'' ):format( aType, aTag, aLabel ) end end local function Name( aName, anAddress ) local WikiContent = require( 'WikiContent' ) local aName = aName or '' if aName == 'anonymous' then local IPMnemonic = require( 'IPMnemonic' ) aName = IPMnemonic[ anAddress ] or aName end aName = WikiContent[ aName ] or '' aName = aName:gsub( '%W', ' ' ) aName = Capitalize( aName ) return aName end local function Location( aContent, anAddress ) local aLocation = aContent.location if not aLocation then local IPLocation = require( 'IPLocation' ) aLocation = IPLocation[ anAddress ] aContent.location = aLocation end if aLocation then local aBuffer = {} if aLocation.city and aLocation.city ~= '-' then aBuffer[ #aBuffer + 1 ] = aLocation.city elseif aLocation.region and aLocation.region ~= '-' then aBuffer[ #aBuffer + 1 ] = aLocation.region end if aLocation.country and aLocation.country ~= '-' then aBuffer[ #aBuffer + 1 ] = aLocation.country end aLocation = table.concat( aBuffer, ', ' ) end return aLocation end function By( aContent, hasTitle ) local URL = require( 'URL' ) local aURL = URL( aContent.by ) local anUser = aURL.user local anAddress = aURL.password local aHost = aURL.host local aName = Name( anUser, anAddress ) local aLocation = Location( aContent, anAddress ) local aBuffer = {} if hasTitle then aBuffer[ #aBuffer + 1 ] = ( '%s' ):format( Encode( anAddress or '?' ), Encode( aName ) ) else aBuffer[ #aBuffer + 1 ] = Encode( aName ) end if hasTitle and aLocation then aBuffer[ #aBuffer + 1 ] = ( ' from %s' ):format( Encode( aHost or '?' ), Encode( aLocation ) ) elseif aLocation then aBuffer[ #aBuffer + 1 ] = ( ' from %s' ):format( aLocation ) end return table.concat( aBuffer ) end -------------------------------------------------------------------------------- -- Metamethods -------------------------------------------------------------------------------- function meta:__concat( aValue ) return tostring( self ) .. tostring( aValue ) end function meta:__tostring() return ( '%s/%s' ):format( self._NAME, self._VERSION ) end