root/HTTP/WikiContentService.lua

Revision 1464 (checked in by rsz, 3 days ago)

cleanup

Line 
1 --------------------------------------------------------------------------------
2 -- Title:               WikiContentService.lua
3 -- Description:         Like a square peg in a round hole
4 -- Author:              Raphaël Szwarc http://alt.textdrive.com/lua/
5 -- Creation Date:       January 30, 2007
6 -- Legal:               Copyright (C) 2007 Raphaël Szwarc
7 --                      Under the terms of the MIT License
8 --                      http://www.opensource.org/licenses/mit-license.html
9 --------------------------------------------------------------------------------
10
11 -- import dependencies
12 local HTTP = require( 'HTTP' )
13 local Template = require( 'Template' )
14 local URL = require( 'URL' )
15 local URLPath = require( 'URLPath' )
16 local WikiContent = require( 'WikiContent' )
17 local WikiService = require( 'WikiService' )
18
19 local BaseLink = WikiService.BaseLink
20 local DateLink = WikiService.DateLink
21 local FeedLink = WikiService.FeedLink
22 local IndexLink = WikiService.IndexLink
23
24 local ContentIterator = WikiService.ContentIterator
25 local NameIterator = WikiService.NameIterator
26 local Today = WikiService.Today
27 local Yesterday = WikiService.Yesterday
28 local ThisWeek = WikiService.ThisWeek
29
30 local Encode = WikiService.Encode
31 local FormatDate = WikiService.FormatDate
32 local FormatDateTime = WikiService.FormatDateTime
33 local GetType = WikiService.GetType
34 local HTML = WikiService.HTML
35 local Path = WikiService.Path
36 local Tag = WikiService.Tag
37
38 local os = require( 'os' )
39 local table = require( 'table' )
40
41 local getmetatable = getmetatable
42 local require = require
43 local setmetatable = setmetatable
44 local tonumber = tonumber
45 local tostring = tostring
46
47 --------------------------------------------------------------------------------
48 -- WikiContentService
49 --------------------------------------------------------------------------------
50
51 module( 'WikiContentService' )
52 _VERSION = '1.0'
53
54 local self = setmetatable( _M, {} )
55 local meta = getmetatable( self )
56
57 --------------------------------------------------------------------------------
58 -- Utilities
59 --------------------------------------------------------------------------------
60
61 local function Message( self )
62     local aModification = self.content.modification or 0
63     local aTime = os.time()
64     local anInterval = aTime - aModification
65    
66     if anInterval < 30 then
67         local WikiMessage = require( 'WikiMessage' )
68         local aText = FormatDateTime( self.content.modification )
69         local aMessage = WikiMessage( ( 'Updated on %s.' ):format( aText ) )
70        
71         return aMessage
72     end
73 end
74
75 local function EditorLink( self )
76     local aContent = self.content
77     local aTitle = Encode( aContent.data.title )
78    
79     if aContent.canWrite then
80         local aLink = Encode( self.path( 'editor' ) )
81    
82         return ( '<a href=\'%s\' title=\'Editor\' rel=\'nofollow\'>%s</a>' ):format( aLink, aTitle )
83    
84     end
85    
86     return aTitle
87 end
88
89 local function Robot( aModification )
90     local aTime = os.time()
91     local anInterval = aTime - ( aModification or aTime )
92    
93     if anInterval > 86400 then
94         return 'index,follow'
95     end
96    
97     return 'noindex,nofollow'
98 end
99
100 local function Referer( self )
101     if self.content.name ~= 'main'
102     and self.content.name ~= 'markdown-syntax-reference'
103     and HTTP.request.header[ 'referer' ] then
104         local URL = require( 'URL' )
105         local aURL = URL( HTTP.request.header[ 'referer' ] )
106         local aReferer = self.toObject( self, aURL )
107        
108         if aReferer
109         and aReferer.content.name ~= 'date'
110         and aReferer.content.name ~= 'index'
111         and aReferer.content.name ~= 'main'
112         and aReferer.content.name ~= 'markdown-syntax-reference'
113         and aReferer.content.name ~= 'recent'
114         and aReferer.content.name ~= 'search'
115         and aReferer.content.exists then
116             self.content.link = aReferer.content.name
117         end
118     end
119 end
120
121 --------------------------------------------------------------------------------
122 -- DAV Utitities
123 --------------------------------------------------------------------------------
124
125 local function DAVIterator( aContent )
126     local aName = ( '%s.txt' ):format( aContent.data.title )
127     local aCreation = aContent.creation
128     local aModification = aContent.modification
129     local aSize = aContent.text:len()
130     local aResource = { name = aName, mode = 'file', creation = aCreation, modification = aModification, size = aSize }
131     local aList = { aResource }
132     local aCount = #aList
133     local anIndex = 1
134    
135     aResource = { name = 'file', mode = 'directory', modification = aModification, size = 0 }
136     aList[ #aList + 1 ] = aResource
137
138     aCount = #aList
139    
140     return function()
141         if anIndex <= aCount then
142             local aResource = aList[ anIndex ]
143            
144             anIndex = anIndex + 1
145            
146             return aResource
147         end
148     end
149 end
150
151 local function DAVResource( aContent )
152     local anIterator = DAVIterator( aContent )
153     local aCreation = aContent.creation
154     local aModification = aContent.modification
155     local aResource = { iterator = anIterator, mode = 'directory', creation = aCreation, modification = aModification, size = 0 }
156    
157     return aResource
158 end
159
160 --------------------------------------------------------------------------------
161 -- Default content
162 --------------------------------------------------------------------------------
163
164 local function Log()
165     local aContent = WikiContent( 'Log' )
166    
167     if not aContent.exists then
168         local WikiDate = require( 'WikiDate' )
169         local WikiFinder = require( 'WikiFinder' )
170         local WikiRecent = require( 'WikiRecent' )
171         local WikiSearch = require( 'WikiSearch' )
172        
173         aContent.by = 'file://nanoki@localhost/'
174         aContent.title = 'Log'
175         aContent.text = '   '
176         aContent()
177        
178         WikiDate[ aContent.name ] = aContent.creation
179         WikiFinder[ aContent.name ] = WikiService.Text( aContent )
180         WikiRecent[ aContent.name ] = aContent.modification
181         WikiSearch[ aContent.name ] = aContent.name
182     end
183 end
184
185 local function Nanoki()
186     local aContent = WikiContent( 'nanoki' )
187    
188     if not aContent.exists then
189         local File = require( 'File' )
190         local WikiDate = require( 'WikiDate' )
191         local WikiFinder = require( 'WikiFinder' )
192         local WikiRecent = require( 'WikiRecent' )
193         local WikiSearch = require( 'WikiSearch' )
194         local aPath = ( '%sNanoki' ):format( require( 'Bundle' )() )
195         local aDirectory = File( aPath )
196         local aTemplate = Template[ 'Nanoki.txt' ]
197        
198         aContent.by = 'file://nanoki@localhost/'
199         aContent.title = 'Nanoki'
200         aContent.text = tostring( aTemplate )
201         aContent()
202        
203         for aFile in aDirectory() do
204             aContent.file = aFile
205         end
206        
207         aContent.canWrite = false
208         WikiDate[ aContent.name ] = aContent.creation
209         WikiFinder[ aContent.name ] = WikiService.Text( aContent )
210         WikiRecent[ aContent.name ] = aContent.modification
211         WikiSearch[ aContent.name ] = aContent.name
212     end
213 end
214
215 local function Syntax()
216     local aContent = WikiContent( 'markdown-syntax-reference' )
217    
218     if not aContent.exists then
219         local WikiDate = require( 'WikiDate' )
220         local WikiFinder = require( 'WikiFinder' )
221         local WikiRecent = require( 'WikiRecent' )
222         local WikiSearch = require( 'WikiSearch' )
223         local aTemplate = Template[ 'MarkdownSyntaxReference.txt' ]
224        
225         aContent.by = 'file://nanoki@localhost/'
226         aContent.title = 'Markdown syntax reference'
227         aContent.text = tostring( aTemplate )
228         aContent()
229        
230         WikiDate[ aContent.name ] = aContent.creation
231         WikiFinder[ aContent.name ] = WikiService.Text( aContent )
232         WikiRecent[ aContent.name ] = aContent.modification
233         WikiSearch[ aContent.name ] = aContent.name
234     end
235 end
236
237 Log()
238 Nanoki()
239 Syntax()
240
241 --------------------------------------------------------------------------------
242 -- Service methods
243 --------------------------------------------------------------------------------
244
245 self.toURL = function( aService, anObject )
246     if anObject.content then
247         local aPath = URLPath()
248        
249         aPath[ #aPath + 1 ] = anObject.content.name
250        
251         return URL( aService.prefix .. aPath )
252     end
253 end
254
255 self.toObject = function( aService, aURL )
256     local aPath = aURL.path
257     local aName = aPath[ 1 ] or 'main'
258     local aContent = WikiContent( aName )
259    
260     if aContent then
261         local WikiContentService = require( 'WikiContentService' )
262         local aService = WikiContentService( aContent )   
263    
264         return aService
265     end
266 end
267
268 function self:getHtml()
269     if self.content and self.content.exists then
270         local aLayoutTemplate = Template[ 'WikiLayout.txt' ]
271         local aTemplate = Template[ 'WikiContentService.txt' ]
272         local aLinkTemplate = aTemplate[ 'link' ]
273         local aLinkIterator, aLinkCount = ContentIterator( self.content.link )
274         local aDate = os.date( '!*t', self.content.creation )
275         local aDateLink = DateLink( aDate.year, aDate.month, aDate.day )
276        
277         Referer( self )
278    
279         aTemplate[ 'editorLink' ] = EditorLink( self )
280         aTemplate[ 'version' ] = Encode(self.content.version )
281         aTemplate[ 'title' ] = Encode( self.content.data.title )
282         aTemplate[ 'dateLink' ] = Encode( aDateLink )
283         aTemplate[ 'creation' ] = Encode( FormatDate( self.content.creation ) )
284         aTemplate[ 'modification' ] = Encode( FormatDateTime( self.content.modification ) )
285         aTemplate[ 'tag' ] = Tag( self.content.modification )
286         aTemplate[ 'by' ] = Encode( URL( self.content.by ).host or 'localhost' )
287         aTemplate[ 'message' ] = Message( self )
288         aTemplate[ 'content' ] = HTML( self.content )
289        
290         for aContent, aURL in aLinkIterator do
291             local aNameTemplate = aLinkTemplate[ 'names' ]
292    
293             aNameTemplate[ 'href' ] = Encode( aURL.path )
294             aNameTemplate[ 'name' ] = Encode( aContent.title )
295            
296             aLinkTemplate[ 'names' ] = aNameTemplate
297         end
298        
299         if aLinkCount == 0 then
300             aLinkTemplate = nil
301         end
302        
303         aTemplate[ 'link' ] = aLinkTemplate
304        
305         aLayoutTemplate[ 'baseLink' ] = Encode( BaseLink() )
306         aLayoutTemplate[ 'indexLink' ] = Encode( IndexLink( self.content.prefix ) )
307         aLayoutTemplate[ 'dateLink' ] = Encode( aDateLink )
308         aLayoutTemplate[ 'feedLink' ] = FeedLink( self, 1 )
309         aLayoutTemplate[ 'path' ] = Path( self )
310         aLayoutTemplate[ 'query' ] = nil
311         aLayoutTemplate[ 'robot' ] = Robot( self.content.modification )
312         aLayoutTemplate[ 'title' ] = Encode( self.content.title )
313         aLayoutTemplate[ 'content' ] = aTemplate
314                
315         return tostring( aLayoutTemplate )
316     end
317    
318     return nil, self.path( 'editor' )
319 end
320
321 function self:getLua()
322     if self.content and self.content.exists then
323         local Data = require( 'Data' )
324    
325         HTTP.response.header[ 'content-type' ] = 'text/plain; charset=utf-8'
326    
327         return Data( self.content.data )
328     end
329    
330     return nil, self.path( 'editor' )
331 end
332
333 function self:getTxt()
334     if self.content and self.content.exists then
335         HTTP.response.header[ 'content-type' ] = 'text/plain; charset=utf-8'
336    
337         return self.content.text
338     end
339    
340     return nil, self.path( 'editor' )
341 end
342
343 function self:getXml()
344     if self.content and self.content.exists then
345         local WikiFeed = require( 'WikiFeed' )
346         local anIterator = NameIterator( { self.content.name } )
347         local aGenerator = HTML
348         local aContext = { title = self.content.data.title, link = HTTP.request.url, creation = self.content.creation }
349                
350         HTTP.response.header[ 'content-type' ] = 'application/atom+xml; charset=utf-8'
351    
352         return tostring( WikiFeed( anIterator, aGenerator, aContext ) )
353     end
354
355     return nil, self.path( 'editor' )
356 end
357
358 function self:get( aType )
359     if self.content and self.content.exists then
360         return GetType( self, aType )
361     end
362    
363     return nil, self.path( 'editor' )
364 end
365
366 function self:getEditor( aVersion )
367     local WikiEditorService = require( 'WikiEditorService' )
368     local aVersion = tonumber( aVersion )
369     local aContent = WikiContent( self.content.name, aVersion )
370    
371     if aContent.canWrite then
372         if not aContent.exists then
373             aContent = self.content
374         end
375        
376         if not aContent.exists then
377             HTTP.response.status.code = 404
378             HTTP.response.status.description = 'Not Found'
379         end
380        
381         return WikiEditorService( aContent, self )
382     end
383 end
384
385 function self:getFile()
386     local WikiContentFileService = require( 'WikiContentFileService' )
387     local aService = WikiContentFileService( self.content )
388    
389     return aService
390 end
391
392 --------------------------------------------------------------------------------
393 -- DAV service methods
394 --------------------------------------------------------------------------------
395
396 function self:options()
397     HTTP.response.header[ 'allow' ] = 'DELETE, GET, HEAD, LOCK, MOVE, OPTIONS, PROPFIND, PUT, UNLOCK'
398     HTTP.response.header[ 'content-type' ] = 'text/plain'
399    
400     return HTTP.response.header[ 'allow' ]
401 end
402
403 function self:propfind()
404     local aContent = self.content
405    
406     if aContent and aContent.exists then
407         local WikiDAV = require( 'WikiDAV' )
408         local aResource = DAVResource( aContent )
409
410         return WikiDAV( aResource ):propfind()
411     end
412 end
413
414 --------------------------------------------------------------------------------
415 -- Metamethods
416 --------------------------------------------------------------------------------
417
418 function meta:__call( aContent )
419     local aService = { content = aContent }
420    
421     setmetatable( aService, self )
422    
423     return aService
424 end
425
426 function meta:__concat( aValue )
427     return tostring( self ) .. tostring( aValue )
428 end
429
430 function meta:__tostring()
431     return ( '%s/%s' ):format( self._NAME, self._VERSION )
432 end
433
434 function self:__index( aKey )
435     local aValue = getmetatable( self )[ aKey ]
436    
437     if not aValue and aKey:find( '^get.+' ) then
438         local WikiContentFileService = require( 'WikiContentFileService' )
439         local aName = aKey:match( '^get(.+)$' )
440         local aService = WikiContentFileService( self.content, aName )
441
442         aService.name = aName
443        
444         return function( self, anExtension )
445             aService.name = ( '%s.%s' ):format( aName, anExtension or '' )
446        
447             return aService
448         end
449     end
450    
451     self[ aKey ] = aValue
452    
453     return aValue
454 end
455
456 function self:__concat( aValue )
457     return tostring( self ) .. tostring( aValue )
458 end
459
460 function self:__tostring()
461     return tostring( self.content.data.title )
462 end
Note: See TracBrowser for help on using the browser.