[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>&nbsp;<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 '&nbsp;&nbsp;' +
            formatWiki(getSynopsisFile(title)) +
           '&nbsp;&nbsp;'
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('&nbsp;<a href="edit=' + title + '">EditPage</a>') +
            td(getSynopsis(title)) +
              td(
                 createLink('RegexSearch') +
                 ': <input type="edit" name="search">&nbsp;', '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&nbsp;Synopsis:&nbsp;<input name="synopsis" type="edit" size=80 value="' +
            getSynopsisFile(title) + '"> ' +
            createLink('MinorEdit') +
            '&nbsp;<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(/&/, '&amp;').
                gsub(/</, '&lt;').
                gsub(/>/, '&gt;')
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 = [ ['&','&amp;'], ['<','&lt;'], ['>','&gt;'],
             [/'''(.*?)'''/, '<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>&nbsp;</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&nbsp;<font size=1>%p</font>'

            if lastDay != tyme.day or lastMon != tyme.mon or lastYear != tyme.year then
                format += ' %a&nbsp;%d&nbsp;%b&nbsp;%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