[Devel-conf] HLOOK
Michael Shigorin
=?iso-8859-1?q?mike_=CE=C1_osdn=2Eorg=2Eua?=
Чт Авг 11 14:28:45 MSD 2005
On Thu, Aug 11, 2005 at 02:14:14PM +0400, Denis Ovsienko wrote:
> > > > 2. Соотв. цепляться к http мы будем через СGI.
> > > Брали бы вы PHP и действительно не изобретали велосипед.
> > php не может того что может alterator. Кроме того, в данном
> > случае мы сами в состоянии сгенерить тот html который нам
> > нужен и php тут совершенно ни чем помочь не может.
> При всём уважении к господам проектировщикам после таких
> комментариев я вынужден умыть руки, хотя web-морды под Linux
> пишу с 1999 года.
Денис, php -- это слишком много ненужных фич (и кода) здесь.
Я тоже веб-морды под фрюниксами где-то с тех же лет делал. :)
Вон чего пооставалось даже:
home:/var/ftp/pub/Linux/utils/web> l | hands
-rw-rw-r-- 1 mike mike 61670 Mar 23 1999 boa-0.92q.tar.gz
-rw-rw-r-- 1 mike mike 19737 Mar 26 2000 htmsysinfo-1.5.tar.gz
-rw-rw-r-- 1 mike mike 98366 Oct 23 1999 thttpd-2.04.tar.gz
(там где-то была и сишная cgi-библиотека, но это дело сильно
не понравилось)
PS 2 inger: во, в аттаче -- мелкая standalone wiki на Ruby.
В качестве примера сервера/CGI.
--
---- WBR, Michael Shigorin <mike на altlinux.ru>
------ Linux.Kiev http://www.linux.kiev.ua/
----------- следующая часть -----------
#!/usr/bin/ruby
# a tiny Ruby wiki
require 'socket'
require 'trace'
require 'cgi'
Host = ARGV[0]
Port = ARGV[1].to_i()
RecentChangeCount = 20
$art = '<img src="http://www.greencheese.org/penBird.gif" border=0>'
# $art = '<img src="icon.gif">'
def openServer
printf("Opening server on host %s, port %i\n", Host, Port)
server = TCPServer.new( Host, Port )
puts "Opened"
return server
end
# TODO minor edits
# search -> graph -> don't render half a graph
# put the wiki source on a page
# hallow SearchMe
# case sensitivity despite DOS
# clean up the title tag of a clicker - no formatting, easy word-wrap
# more recent changes
# Search at top
# TreeOfLife
# don't croak over regex search syntax errors
# searches also hit info
# X(oh this hurts)ML file format
# Don't keep a lock on any file after page paint time
# user id in date stamp system
# use string squeeze somewhere ;-)
# a little more assert_match around here
# alt tags inside graphs
# put the info in the alt-tag of the title
# make RecentChanges a real page, like NerveCenter
# tooltip for a RemoteLink should be its complete URL
# escape properly inside info edit areas
# dunn:
# stash pages as html
# escape properly inside text edit areas
# put the focus into the edit field
# wildcards in the searches ####
# put the change synopsis into the title tag of a clicker
# Split a remote link into Local:remote
# let => make text get very big
# let [] change text background to orange
# ability to expand the target half of a RemoteLink >inside< the redirection url
$mimeTypes = { 'jpg' => 'image/jpg', 'gif' => 'image/gif', 'png' => 'image/png',
'html' => 'text/html', 'pdf' => 'application/pdf' }
$response = {
200 => "HTTP/1.0 200 Okay\nServer: ws30\nContent-type: %s\n\n",
301 => "HTTP/1.0 301 Moved\nServer: ws30\nContent-type: text/plain\n\nLocation: %s\n\nmoved",
404 => "HTTP/1.0 404 Not Found\nServer: ws30\nContent-type: text/plain\n\n\n%s not found",
}
LocalLink = '\\b([A-Z][a-z]+){2,}\\b' # the heart of Wikidom... ;-)
def writeFile name, guts
File.open(name, 'w') do |f| f.write(guts) end
end
def getRequest stream
method = nil
uri = nil
contentLength = 0
while 1 do
line = stream.readline()
if line.strip().length() == 0 then
break
elsif method == nil then
method, uri, protocol = line.split(' ')
end
if /^content-length: /.match(line.downcase()) then
contentLength = line.split(': ')[1].strip.to_i()
end
end
warning = ''
if method == 'POST' then
line = stream.read(contentLength)
substance = decodeURL(line)
title = uri.slice(1..-1)
fileName = wiki(title)
timeStampThen = substance['timestamp']
timeStampNow = '0'
timeStampNow = File.mtime(fileName).tv_sec.to_s() if File.exist?(fileName)
minorEdit = substance['MinorEdit']
if timeStampThen < timeStampNow then
warning = 'Someone has changed this page. ' +
'To recover your content, ' +
'<li>hit Back' +
'<li>copy your text into an editor' +
'<li>hit Refresh' +
'<li>merge your changes with the page\'s current text' +
'<li>hit Save again<hr>'
else
# TODO replace all this fun with XML!
modTime = if File.exist?(fileName) then File.mtime(fileName) else Time.now() - 60*60*24*256 end
writeFile(fileName, substance['text'])
writeFile(info(title), substance['synopsis'])
if minorEdit == 'on' then
File.utime( modTime, modTime, fileName)
end
# toast every page backup that might have an incorrect notion of this page
getWikiPageList.each do |page|
contents = readFile(page)
if contents =~ /\btitle\b/ then
html_file = page.slice(0...-5)
html_file += '.html'
puts "unlinking #{html_file}"
File.unlink(html_file) if File.exists? html_file
end
end
end
end
return uri, warning
end
def info tag
return tag + '.info'
end
# TODO replace split(\n) with each_line
# TODO clone PerlStringTest.exe in Ruby
def listDirectory uri
return 'directory none of your business'
end
def headerStuff title, onload = ''
return '<head><title>' + title + "</title></head>\n" +
'<body text="black" bgcolor="white"' + onload + '>' +
'<h1> <a href="/NerveCenter">' + $art + '</a> ' +
'<a href="/?search=' + title + '">' + title + '</a></h1>'
end
def getSynopsisFile(title)
synopsis = ''
infoFile = info(title)
if File.exist?(infoFile) then # TODO merge duplication
synopsis = File.open(infoFile).read()
end
return synopsis
end
def getSynopsis(title)
return ' ' +
formatWiki(getSynopsisFile(title)) +
' '
end
['em', 'th', 'tr', 'td'].each do |thing|
eval( "def #{thing}(q,spec = '');" +
" return '<#{thing} ' + spec + '>' + q + \"</#{thing}>\";" +
"end"
)
end
def createLink urlage, contents = '', leap = ''
title = ''
contents = urlage if contents == ''
if leap == '' then
synopsis = getSynopsisFile(contents)
if synopsis != '' then
title = ' title="' + synopsis + '"'
end
end
return "<a #{leap}href=\"#{urlage}\"#{title}>#{contents}</a>"
end
def formatFile(title, contents, extra = '')
return '<form method="GET" action="/">' +
headerStuff(title) + '<table width="85%">' +
tr(
td(
'<table width="100%" cellpadding=6 border=1>' +
tr(td(formatWiki(contents) + "\n" + extra + "\n")) +
"</table>\n", 'colspan=3'
)
) +
tr(
td(' <a href="edit=' + title + '">EditPage</a>') +
td(getSynopsis(title)) +
td(
createLink('RegexSearch') +
': <input type="edit" name="search"> ', 'align="right"'
)
) +
'</table></form></body>'
end
def readFile path
File.open(path, 'rb') do |f| return f.read() end
end
def getPage title
readStyleSheet()
path = wiki(title)
html_file = title + '.html'
# TODO fix me!
if File.exists?(html_file) and
File.mtime(html_file).tv_sec >= File.mtime(path).tv_sec and
File.size(html_file) > 0 and
false then
return readFile(html_file)
end
File.open( path ) do |f| # TODO begin/rescue here
extra = ''
extra = getNerveCenter() if title == 'NerveCenter'
extra = getUnrequitedLinks() if title == 'WikiUnrequitedLinks'
contents = formatFile(title, f.read(), extra)
writeFile(html_file, contents)
return contents
end
end # ContractiveDelegation aids testage ;-)
def getUnrequitedLinks
html = ''
unLinks = []
# this is brutally slow, but correct & easy to code
# TODO - use the HTML backup, as soon as it is healthy
getWikiPageList().
each do |page|
if page != 'WikiUnrequitedLinks.wiki' then
words = getPage(page[0..-6])
# <a href="edit=SnoOpy">?</a>
unLinks += words.scan(/\<a href="edit=([^"]+)">\?\<\/a>/)
end
end
unLinks.flatten!
unLinks.sort!
unLinks.uniq!
unLinks.each do |link|
if link != 'RecentChanges' then # TODO yank this
html += '<a target="_blank" href="/?search=' + link + '">search</a> - '
html += link + createLink('edit=' + link, '?')
html += '<br>'
end
end
return html
end
def wiki title
return title + '.wiki'
end
def editFile title
path = wiki(title)
guts = ''
if File.file?(path) then
File.open( path ) do |f| # TODO begin/rescue here
guts = f.read()
end
else
guts = sprintf('Describe %s here.', title)
end
timeStamp = '0'
timeStamp = File.mtime(path).tv_sec.to_s() if File.exist?(path)
onload = ' onload="document.f.text.focus()"'
return headerStuff(title, onload) +
'<form name="f" method="POST" action="' + title + '">' +
'<textarea name="text" style="width:85%" rows=20 cols=80 wrap="virtual">' +
cleanContents(guts) +
'</textarea><br>' + "\n" +
'Change Synopsis: <input name="synopsis" type="edit" size=80 value="' +
getSynopsisFile(title) + '"> ' +
createLink('MinorEdit') +
' <input type="checkbox" name="MinorEdit"> ' +
'<input type="hidden" name="timestamp" value = "' + timeStamp + '">' +
'<input type="submit" value = "Save">' +
'</body>'
end
def cleanContents guts # TODO merge me with the other HTML filter?
return guts.gsub(/&/, '&').
gsub(/</, '<').
gsub(/>/, '>')
end
def getContent uri, warning
puts 'fetching:', uri
# TODO add the client addy
if uri == '/RecentChanges' then
return [ 200, 'text/html', recentChanges() ]
else
# begin
title = uri.slice(1..-1)
title = 'FrontPage' if title == ''
if uri.slice(1..5) == 'edit=' then
return [ 200, 'text/html', editFile(title.slice(5..-1)) ]
end
if uri.slice(1..8) == '?search=' then
token = title.slice(8..-1)
sample = CGI::unescape(token)
return [ 200, 'text/html', search(sample) ]
end
path = wiki(title)
if File.file?( path ) then
return [ 200, 'text/html', warning + getPage( title ) ]
else
fileName = '.' + uri
puts 'filename', fileName.inspect()
return [ 200, 'image/png', readFile(fileName) ] if File.file?(fileName)
end
return [ 404, uri ]
end # TODO actually call the sendResponse here?
end
# def getMime uri
# type = $mimeTypes[ uri.split('.')[-1]]
# type = 'text/plain' if type == nil
# return type
# end
def sendResponse stream, content
$responseType = $response[content[0]]
content.push("")
response = sprintf($responseType, content[1] )
stream.write( response )
stream.write( content[2] )
stream.write( "\n" )
end
$style = []
def readStyleSheet
sheet = 'WikiStyleSheet.wiki'
$style = []
if File.exists?(sheet) and
File.size(sheet) > 0 then # TODO write a function fileIsRelevant
contents = readFile(sheet)
contents = contents.split("\n")
contents.each do |line|
if line[0] == '['[0] then
begin
gsubber = eval(line)
gsubber[0] = Regexp.new(/(\<[^>]*>)|(#{gsubber[0].source()})/)
$style.push(gsubber)
rescue
puts $!
puts line.inspect()
end
end
end
end
end
def decodeURL post
result = {}
post.split('&').each do
|token|
token = CGI::unescape(token)
key, value = token.split(/=/, 2)
result[key] = value
end
return result
end
# TODO deal with <>& inside urlage
# TODO pre tags
def runGraphViz graphName, graphLines
dotName = graphName + '.dot'
mapName = graphName + '.map'
pngName = graphName + '.png'
writeFile(dotName, graphLines)
# TODO sample the errors and report them in the returned page (as a graph? ;-)
tossErr = ''
tossErr = ' 2>/dev/null' if RUBY_PLATFORM =~ /linux/
system ("dot -Tcmap #{dotName} -o #{mapName}" + tossErr)
map = readFile(mapName)
map = yield(map) if block_given?
system ("dot -Tpng #{dotName} -o #{pngName}" + tossErr)
mapHtml = ''
if map != '' then
mapHtml = ' usemap="#' + mapName +
'"><map name="' + mapName +
'">' + map +
'</map'
end
return "<img src=\"#{pngName}\" border=0#{mapHtml}>"
end
class WikiFormatter
def initialize
@depth = @ulOwed = 0
@graphName = ''
@graphLines = ''
@inGraph = false
@inPre = false
end
def cleanForHTML str
standard = [ ['&','&'], ['<','<'], ['>','>'],
[/'''(.*?)'''/, '<strong>\\1</strong>' ],
[/''(.*?)''/, '<em>\\1</em>' ], [/^----+/, '<hr>'],
]
standard.each do |wiki, html| str.gsub!(wiki, html) end
# TODO there has to be a smaller but sicker way to do this
str = formatLinks(str)
$style.each do |wiki, html|
str.gsub!(wiki) do |quip|
if quip[0] == '<'[0] or quip == '' then
quip
else
html.gsub(/\\1/, $+)
end
end
end
return str
end
def tagListDepth navel
got = /^([ ]+)\*(.*)$/.match(navel)
pre = got[1]
post = got[2]
post.strip!
nuDepth = pre.length()
preStr = ''
if nuDepth == @depth then
preStr += '</li><li>'
elsif nuDepth > @depth then
(0...(nuDepth - @depth)).each do
preStr += '<ul><li>'
@ulOwed += 1
end
else
preStr = downLevels(@depth - nuDepth)
preStr += '<li>'
end
@depth = nuDepth
return "#{preStr}#{cleanForHTML(post)}\n"
end
GraphCode = '^(digraph|graph)\\s+([a-zA-Z])+'
def beginGraph line
if !@inGraph and line =~ /#{GraphCode}\s*{\s*$/ then
@graphName = /#{GraphCode}/.match(line)[0]
@graphName = @graphName.split(' ')[1]
@graphLines += line + "\n"
@inGraph = true
return true
end
return false
end
attr_reader :graphName, :graphLines # TODO hide these again
def finishGraph
@inGraph = false
@graphLines += "}\n"
return runGraphViz(@graphName, @graphLines)
end
def formatLine line
nuSource = ''
if beginGraph(line) then
nil
elsif @inGraph then
if line =~ /^}/ then
nuSource += finishGraph()
line = line.slice(1..-1)
nuSource += cleanForHTML(line) + "\n"
else
@graphLines += line + "\n"
end
else
if /^([ ]+)\*/.match(line) then
line = tagListDepth(line)
else
if @ulOwed > 0 then
preStr = downLevels(@ulOwed)
# line = preStr + cleanForHTML(line) + "\n"
line += "\n"
nuSource += preStr
end
if line =~ /^\s/ then
if @inPre then
line = cleanForHTML(line)
else
@inPre = true
line = '<pre>' + cleanForHTML(line)
end
line += "\n"
elsif @inPre then
@inPre = false
line = '</pre>' + cleanForHTML(line)
else
line = cleanForHTML(line) + "\n"
end
end
nuSource += line
end
return nuSource
end
def format source
initialize()
nuSource = ''
source.split("\n").each do
|line|
if (line == "" or line == "\r") and !@inPre then
nuSource += "<p>\n"
else
nuSource += formatLine(line)
end
end
if @inPre then
nuSource += '</pre>'
end
nuSource += downLevels(@ulOwed)
return nuSource
end
def downLevels down
preStr = ''
while down > 0 do
preStr += '</li></ul>'
@ulOwed -= 1
@depth -= 1
down -= 1
end
return preStr
end
Uri = '[.a-zA-Z0-9\/\-@&?=:_;~+!#$%^*()\[\]{}]+'
HypertextProtocols = 'http|https'
LinkingProtocols = 'mailto|ftp' # screw gopher: ;-)
Protocols = '(' + HypertextProtocols + '|' + LinkingProtocols + ')'
RemoteLink = '(?!RemoteLink:)' + LocalLink + ':(' + Uri + ')?'
Links = /#{Protocols}:#{Uri}|#{RemoteLink}|#{LocalLink}/
HypertextMatch = /^(#{HypertextProtocols})$/
def outerLink urlage, contents
return createLink(urlage, contents, 'target="_blank" ')
end
def interpretLink name
start = name.split(':')[0]
if start =~ /^(#{Protocols})$/ then
ext = name.split('.')[-1].downcase()
if start =~ HypertextMatch and
ext =~ /^(png|jpg|jpeg|gif)$/ then
return outerLink(name, "<img src=\"#{name}\" border=0>")
else
if start =~ HypertextMatch then
return outerLink(name, name)
end
return createLink(name, name)
end
elsif name =~ /^#{RemoteLink}$/ then
linkHTML = name
plink, uri = splitFirstColon(name)
file = wiki(plink)
if File.file?(file) then
File.open(file) do |f|
line = f.readline()
line.strip!
base, rest = splitFirstColon(line)
if base == 'RemoteLink' then
urlage = sprintf(rest, uri)
urlage += uri if urlage.size() == rest.size()
return "<a href=\"#{plink}\">#{plink}</a>:" +
"<a target=\"_blank\" href=\"#{urlage}\">#{uri}</a>"
else
linkHTML = formatWiki(plink).strip() + ':'
linkHTML += formatWiki(uri).strip() if uri != nil
end
end
else
linkHTML = plink + createLink('edit=' + plink, '?') # TODO OAOO
return (linkHTML + ':' + uri)
end
else
if (name == 'RecentChanges' or File.file?(wiki(name))) then
return createLink(name, name)
else
return name + createLink('edit=' + name, '?')
end
end
return linkHTML
end
def formatLinks source
source.gsub!(Links) do
|name|
interpretLink(name)
end
# puts source.inspect()
return source # ! ;-)
end
def splitFirstColon str
return str.split(':', 2)
end
end
def formatWiki source
aFormatter = WikiFormatter.new
result = aFormatter.format(source)
# puts result.inspect()
return result
end # method object design pattern...
$greenBar = true
def listHeader title, predicate, middle = ''
$greenBar = true
return headerStuff(title) + '<table border="0">' +
tr(
th(em('Page' ), 'align="center"') +
th(em(middle ), 'align="center"') +
th(em(predicate), 'align="center"')
)
end
def foundObject tag, line, middle = ''
color = if $greenBar then 'bgcolor="LightCyan"' else '' end
$greenBar = !$greenBar
return tr(
td( '<a href="/' + tag + '">' + tag +
"</a> </td>", 'valign="top"' ) +
td(middle, 'valign="top"') +
td(line, 'valign="top"'),
color
) + "\n"
end
# TODO write a package that makes Ruby restart itself if it detects a source change
# TODO write a package that makes Perl restart itself if it detects a source change
# TODO write a package that makes Python restart itself if it detects a source change
# TODO write a package that makes C++ restart itself if it detects a source change
# TODO notice any patterns?
def baseName(f)
return f.split('.')[0]
end
def searchFile f, tag, sample
File.open(f) do |handle|
handle.readlines().each do |line|
if line =~ /#{sample}/i then
return foundObject(tag, formatWiki(line))
end
end
end
return ''
end
def getWikiPageList
return Dir.glob("*.wiki")
end
def search sample
result = listHeader('Search', 'Citation')
matches = 0
# TODO merge with recentChanges ;-)
getWikiPageList().sort{ |a,b| a <=> b }.
each do |f|
# observe this system checks the file contents
# first, then the name. This improves the odds we have
# something in the citation column
# TODO option to grep every line in a page, not just the first hit
tag = baseName(f)
got = searchFile(f, tag, sample)
if got == '' and tag =~ /#{sample}/i then
got = foundObject(tag, '')
end
result += got
matches += 1 if got != ''
end
result += '</table>'
if matches == 0 then
result += '<p>' + em('no matches for `') +
formatWiki(sample).strip() + em('\'...')
end
# TODO shouldn't have to strip after formatWiki
return result + '</body></html>'
end
def getUniqWikiWords page
# this looks horribly inefficient, but we are only working over 20
# files here, and the file system must have them buffered...
contents = readFile(page)
# TODO merge with LocalLink
wikiWords = contents.scan(/ [^:] ( \b (?: [A-Z][a-z]+ ){2,} \b )/x)
return wikiWords.flatten.uniq
end
def populateAltTags map # fix yet another issue with GraphViz
return map.
gsub(/ alt="[A-Za-z]*" /) do
|name|
page = name.slice(6...-2)
info = getSynopsisFile(page)
info = cleanContents(info)
sprintf(" alt=\"%s\" ", info)
end
end
def getNerveCenter
recentChanges = getRecentChangePages()
graph = generateNerveCenter(recentChanges)
# writeFile 'NerveCenter.dot', graph
return runGraphViz('NerveCenter', graph) do
|map|
populateAltTags(map)
end # execute-around pattern
end
def generateNerveCenter recentChanges
graph = "graph nerveCenter {\n" \
" rankdir = LR;\n" \
" ratio = compress;\n" \
" bgcolor = \"gray85\";\n" \
" node [shape = box, style=filled, fillcolor=white, color=white, height = \"0.1\"];\n" \
" size = \"8,11\";\n" \
# " label = \"Nerve Center\";\n"
recentChanges.each do |page|
contents = readFile(page)
page = page.slice(0...-5)
graph += page + " [URL=\"http:" + page + "\"];\n"
# TODO merge with LocalLink
wikiWords = contents.scan(/((?:[A-Z][a-z]+){2,})/)
wikiWords.uniq.each do |words|
word = words[0]
if word != page and
recentChanges.include?(wiki(word)) and
! graph.include?(word + ' -- ' + page) then
graph += page + ' -- ' + word + " [dir="
if getUniqWikiWords(wiki(word)).include? page then
graph += "both"
else
graph += "forward"
end
graph += "];\n"
end
end
end
return graph + '}'
end
def getRecentChangePages()
return getWikiPageList().sort{ |a,b|
File.mtime(b) <=> File.mtime(a)
}.
slice(0...RecentChangeCount)
end
def recentChanges
result = listHeader('RecentChanges', 'Time', 'Change Synopsis')
lastDay = nil
lastMon = nil
lastYear = nil
getRecentChangePages().
each{
|f|
tag = baseName(f)
# 9:15 pm Sat 30 Nov 2002 # PST %Z
tyme = File.mtime(f)
format = '%I:%M <font size=1>%p</font>'
if lastDay != tyme.day or lastMon != tyme.mon or lastYear != tyme.year then
format += ' %a %d %b %Y'
end
strTime = tyme.strftime(format)
lastDay = tyme.day
lastMon = tyme.mon
lastYear = tyme.year
synopsis = getSynopsis(tag)
result += foundObject(tag, strTime, synopsis) # 1 more arg and we're an object
}
return result + '</table></body></html>'
end
# TODO pin the encoding to US/EN (or FR/FR)
def serve
server = openServer()
begin
while 1 do
stream, client = server.accept()
uri, warning = getRequest( stream )
sendResponse( stream, getContent( uri, warning ) )
stream.close()
end
rescue
puts $!
puts 'shutting down...'
end
server.close()
end
# $art = '<img src = "http://babelfish.altavista.com/static/images/babelfish/systran_big_logo.gif" border=0>'
# $art = 'yo'
serve() if __FILE__ == $0
----------- следующая часть -----------
Было удалено вложение не в текстовом формате...
Имя : =?iso-8859-1?q?=CF=D4=D3=D5=D4=D3=D4=D7=D5=C5=D4?=
Тип : application/pgp-signature
Размер : 189 байтов
Описание: =?iso-8859-1?q?=CF=D4=D3=D5=D4=D3=D4=D7=D5=C5=D4?=
Url : <http://lists.altlinux.org/pipermail/devel-conf/attachments/20050811/6084480a/attachment-0003.bin>
Подробная информация о списке рассылки devel-conf