-------------------------------------------------------------------------------- -- Title: WikiDiffService.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 WikiContent = require( 'WikiContent' ) local WikiService = require( 'WikiService' ) local BaseLink = WikiService.BaseLink local DateLink = WikiService.DateLink local FeedLink = WikiService.FeedLink local IndexLink = WikiService.IndexLink local Encode = WikiService.Encode local Path = WikiService.Path local os = require( 'os' ) local getfenv = getfenv local getmetatable = getmetatable local require = require local setfenv = setfenv local setmetatable = setmetatable local tostring = tostring -------------------------------------------------------------------------------- -- WikiDiffService -------------------------------------------------------------------------------- module( 'WikiDiffService' ) _VERSION = '1.0' local self = setmetatable( _M, {} ) local meta = getmetatable( self ) -------------------------------------------------------------------------------- -- Utilities -------------------------------------------------------------------------------- local function Title( self, isLong ) local aTitle = '' if isLong then aTitle = self.content.data.title aTitle = aTitle .. ' — ' end aTitle = aTitle .. '№ ' aTitle = aTitle .. self.content.version aTitle = aTitle .. ' Δ № ' aTitle = aTitle .. self.version return aTitle end local function TitleHandler() local anEnvironment = {} local aFunction = function( aType, aValue ) if aType == '+' then local anInsert = anEnvironment.insert or 0 anEnvironment.insert = anInsert + 1 return ( ' %s ' ):format( aValue ) elseif aType == '-' then local aDelete = anEnvironment.delete or 0 anEnvironment.delete = aDelete + 1 return ( ' %s ' ):format( aValue ) end return ( ' %s ' ):format( aValue ) end setfenv( aFunction, anEnvironment ) return aFunction end local function TextHandler() local anEnvironment = {} local aFunction = function( aType, aValue ) if aType == '+' then local anInsert = anEnvironment.insert or 0 anEnvironment.insert = anInsert + 1 return ( '

%s

' ):format( aValue ) elseif aType == '-' then local aDelete = anEnvironment.delete or 0 anEnvironment.delete = aDelete + 1 return ( '

%s

' ):format( aValue ) end return ( '

%s

' ):format( aValue ) end setfenv( aFunction, anEnvironment ) return aFunction end local function Delta( aText, anotherText, aHandler, aPattern ) local Diff = require( 'Diff' ) local anEnvironment = getfenv( aHandler ) local aDiff, hasMore = Diff( aText, anotherText, aHandler, aPattern ) return aDiff, anEnvironment.insert or 0, anEnvironment.delete or 0, hasMore end local function Description( anInsert, aDelete, hasMore, aVersion, aNewVersion ) local aCount = anInsert + aDelete local aBetween = ( 'between № %d and № %d' ):format( aVersion, aNewVersion ) if aCount > 0 then local aDescription = '' if hasMore then aDescription = aDescription .. 'Partial comparison. Around ' end if aCount == 1 then aDescription = aDescription .. '1 difference ' .. aBetween .. ' — ' elseif aCount > 1 then aDescription = aDescription .. aCount .. ' differences ' .. aBetween .. ' — ' end if aDelete == 0 then aDescription = aDescription .. 'no delete and ' elseif aDelete == 1 then aDescription = aDescription .. '1 delete and ' elseif aDelete > 1 then aDescription = aDescription .. aDelete .. ' deletes and ' end if anInsert == 0 then aDescription = aDescription .. 'no insert' elseif anInsert == 1 then aDescription = aDescription .. '1 insert' elseif anInsert > 1 then aDescription = aDescription .. anInsert .. ' inserts' end return aDescription end return 'No visible differences ' .. aBetween end -------------------------------------------------------------------------------- -- Service methods -------------------------------------------------------------------------------- function self:get() local aVersion = self.version if aVersion then local aContent = self.content local aNewContent = WikiContent( aContent.name, aVersion ) if aNewContent.exists then local aLayoutTemplate = Template[ 'WikiLayout.txt' ] local aTemplate = Template[ 'WikiDiffService.txt' ] local aDate = os.date( '!*t', aContent.creation ) local aDateLink = DateLink( aDate.year, aDate.month, aDate.day ) local aTitle = Encode( aContent.data.title ) local aNewTitle = Encode( aNewContent.data.title ) local aTitleDelta, aTitleInsert, aTitleDelete = Delta( aTitle, aNewTitle, TitleHandler(), '([%S]+)' ) local aText = Encode( self.content.text ) local aNewText = Encode( aNewContent.text ) local aTextDelta, aTextInsert, aTextDelete, hasMore = Delta( aText, aNewText, TextHandler() ) local anInsert = aTitleInsert + aTextInsert local aDelete = aTitleDelete + aTextDelete local aDescription = Description( anInsert, aDelete, hasMore, aContent.version, aNewContent.version ) aTemplate[ 'title' ] = aTitleDelta or Encode( aContent.data.title ) aTemplate[ 'description' ] = Encode( aDescription ) aTemplate[ 'content' ] = aTextDelta or Encode( aContent.text ):gsub( '(%c%c)', '
' ):gsub( '(%c)', '
' ) aLayoutTemplate[ 'baseLink' ] = Encode( BaseLink() ) aLayoutTemplate[ 'indexLink' ] = Encode( IndexLink( aContent.prefix ) ) aLayoutTemplate[ 'dateLink' ] = Encode( aDateLink ) aLayoutTemplate[ 'feedLink' ] = FeedLink() aLayoutTemplate[ 'path' ] = Path( self ) aLayoutTemplate[ 'query' ] = nil aLayoutTemplate[ 'robot' ] = 'noindex,nofollow' aLayoutTemplate[ 'title' ] = Encode( Title( self, true ) ) aLayoutTemplate[ 'content' ] = aTemplate return tostring( aLayoutTemplate ) end return nil end return nil, ( HTTP.request.url + ( 'n.' .. self.content.version ) ).path( 'n.' .. self.content.lastVersion ) end -------------------------------------------------------------------------------- -- Metamethods -------------------------------------------------------------------------------- function meta:__call( aContent, aParent, aVersion ) local aDiff = { content = aContent, parent = aParent, version = aVersion } setmetatable( aDiff, self ) self.__index = self return aDiff 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 Title( self, false ) end