require "lfs" require "eluadoc" -- Uncomment this when generating offline docs -- local is_offline = true -- Languages in the system -- NOTE: "en" must ALWAYS be the first entry in this array! -- NOTE: all languages must be given in lowercase only! languages = { "en", "pt" } -- Reverse lookup (language to idx) dictionary local langidx = {} for k, v in ipairs( languages ) do langidx[ v ] = k end ------------------------------------------------------------------------------- -- Indexes into our menu table (defined in docdata.lua) name_idx, link_idx, submenu_idx , title_idx = 1, 2, 3, 4 ------------------------------------------------------------------------------- -- "getstr" support (return strings in different languages) -- If defaults to english (but gives a warning) if the string isn't found in the given language -- This table keeps the strings we already emitted warnings for -- After all, we don't want to drive the user crazy local warned = {} function getstr( str, lang ) -- Get the language index from langidx local idx = langidx[ lang ] if not idx then error( string.format( "Invalid language %s", lang ) ) end -- Look from the string in the "translations" table local where for _, v in ipairs( translations ) do if v[ 1 ] == str then where = v break end end if not where then error( string.format( "String %s not found in translations" , str ) ) end -- Try to return the value in the specified language -- If not possible, return the value in english, but issue a warning first local res = where[ idx ] if not res then res = where[ 1 ] if not warned[ str ] then print( string.format( "*** WARNING: translation for '%s' in language '%s' not found!", str, lang ) ) warned[ str ] = true end end return res end ------------------------------------------------------------------------------- -- Generic helper functions -- Remove anchor from a link of the form a/b.../baselink.html#anchor local function get_base_link( name ) return ( name:gsub( "#.*", "" ) ) end -- Get the menu field for a given item and language -- Returns the english name is the field for the specified language can't be found local function get_menu_field( menuitem, lang, fieldidx ) if not menuitem[ fieldidx ] then return nil else if type( menuitem[ fieldidx ] ) == "string" then return menuitem[ fieldidx ] else local lidx = langidx[ lang ] return menuitem[ fieldidx ][ lidx ] or menuitem[ fieldidx ][ 1 ] end end end -- Get the menu name for a given menu item and a language -- Returns the english name if the name for the specified language can't be found local function get_menu_name( menuitem, lang ) return get_menu_field( menuitem, lang, name_idx ) end -- Get the link for a given menu item and a language -- Returns the english name if the name for the specified language can't be found -- If the link field doesn't exists, the name is returned instead local function get_menu_title( menuitem, lang ) return "eLua - " .. ( get_menu_field( menuitem, lang, title_idx ) or get_menu_field( menuitem, lang, name_idx ) ) end -- Set "print" to print indented (with 2 spaces) local oldprint local function indent_print() oldprint = print print = function( ... ) io.write( " " ); oldprint( ... ) end end -- Restore the "regular" print function local function regular_print() print = oldprint end ------------------------------------------------------------------------------- -- File/directory operations helpers -- Copy the given file to the 'dest' directory -- Doesn't do error checking local function copy_file( fname, dst ) local destname = fname if fname:find( "/" ) then -- Get only the filename from the path local sidx for f = #fname, 1, -1 do if fname:sub( f, f ) == "/" then sidx = f break end end destname = fname:sub( sidx + 1 ) end local fsrc = io.open( fname, "rb" ) local fdst = io.open( string.format( "%s/%s", dst, destname ), "wb" ) local data = fsrc:read( "*a" ) fdst:write( data ) fsrc:close() fdst:close() end -- Copy the 'src' directory to the 'dst' directory, going recursively through -- its content. Doesn't do error checking. local function copy_dir_rec( src, dst ) for f in lfs.dir( src ) do local oldf = string.format( "%s/%s", src, f ) local attrs = lfs.attributes( oldf ) if attrs.mode == 'directory' and f ~= "." and f ~= ".." and f ~= ".svn" then local newdir = string.format( "%s/%s", dst, f ) lfs.mkdir( newdir ) copy_dir_rec( oldf, newdir ) elseif attrs.mode == 'file' then copy_file( oldf, dst ) end end end -- Remove a directory recusively -- USE WITH CARE!! Doesn't do much checks :) local function rm_dir_rec( dirname ) for f in lfs.dir( dirname ) do local ename = string.format( "%s/%s", dirname, f ) local attrs = lfs.attributes( ename ) if attrs.mode == 'directory' and f ~= '.' and f ~= '..' then rm_dir_rec( ename ) elseif attrs.mode == 'file' or attrs.mode == 'named pipe' or attrs.mode == 'link' then os.remove( ename ) end end lfs.rmdir( dirname ) end -- Copy a directory to another directory local function copy_dir( src, dst ) local newdir = string.format( "%s/%s", dst, src ) lfs.mkdir( newdir ) copy_dir_rec( src, newdir ) end ------------------------------------------------------------------------------- -- Build the list of files that must be processed starting from the menu data -- Traverse a second (or higher) level menu and add relevant information to flist local function traverse_list( item, parentid, flist ) if not item[ link_idx ] then return end local base = get_base_link( item[ link_idx ] ) if base ~= "" and not flist[ base ] then flist[ base ] = { parentid = parentid, item = item } end if item[ submenu_idx ] then for i = 1, #item[ submenu_idx ] do traverse_list( item[ submenu_idx ][ i ], parentid, flist ) end end end -- Iterate over the menu list, building the list of files that must be -- processed by the doc generator. Returns a dictionary with list, parent_id -- pairs where parent_id is the parent menu of link in themenu local function get_file_list() local flist = {} for i = 1, #themenu do traverse_list( themenu[ i ], i, flist ) end return flist end -- Returns true if the given string begins with the given substring, false otherwise -- The comparation is case-insensitive local function beginswith( str, prefix ) return str:sub( 1, #prefix ):lower() == prefix:lower() end ------------------------------------------------------------------------------- -- Build the navigation data for a given page -- Helper function: format a link starting from language and link -- Links marked as "#" ("null" links) are left alone local function get_link( lang, link ) return link == "#" and "#" or string.format( "%s_%s", lang, link ) end -- Helper for gen_html_nav: generate the submenu(s) for a given top level menu item local function gen_submenus( item, lang, level ) level = level or 1 local data = '' local lidx = langidx[ lang ] local arrptr = 'right arrow' for i = 1, #item do local l = item[ i ] if l[ submenu_idx ] then data = data .. string.rep( " ", level * 2 + 8 ) .. string.format( '
  • %s%s\n', get_link( lang, l[ link_idx ] ), arrptr, get_menu_name( l, lang ) ) data = data .. string.rep( " ", level * 2 + 8 ) .. "
  • \n" else if get_menu_name( l, lang ) then data = data .. string.rep( " ", level * 2 + 8 ) .. string.format( '
  • %s
  • \n', get_link( lang, l[ link_idx ] ), get_menu_name( l, lang ) ) end end end return data end -- Generate the HTML menu structure for the given language and parentid -- If "is_offline" is true, don't generate links to the counter and the BerliOS logo local function gen_html_nav( parentid, lang ) local htmlstr = [[ ]], offline_data ) return htmlstr end ------------------------------------------------------------------------------- -- Build the logo for a given language local function gen_logo( fname, lang ) local numl = #languages local langdata = '' for i = 1, numl do local crtlang = languages[ i ] local hlang = crtlang:sub( 1, 1 ):upper() .. crtlang:sub( 2, -1 ) if lang:lower() == crtlang:lower() then langdata = langdata .. string.format('
    %s
    \n', hlang, crtlang ) else langdata = langdata .. string.format('
    %s
    \n', crtlang:lower(), fname, hlang, crtlang ) end end return string.format( [[
    ]], getstr( "eLua - Embedded Lua", lang ), numl + 1, getstr( "Search", lang ), getstr( "Language", lang ), langdata:sub( 1, -2 ) ) end ------------------------------------------------------------------------------- -- Generate an actual HTML page starting from a template -- Replace the $$HEADER$$ and $$FOOTER$$ with proper data local function gen_html_page( fname, lang ) local entry = flist[ fname ] local parentid = entry.parentid local item = entry.item -- Open and read file local fullname = string.format( "%s/%s", lang, fname ) local f = io.open( fullname, "rb" ) if not f then return nil, string.format( "Error opening %s", fullname ) end local orig = f:read( "*a" ) f:close() -- Check the presence of $$HEADER$$ and $$FOOTER$$ if not orig:find( "%$%$HEADER%$%$" ) or not orig:find( "%$%$FOOTER%$%$" ) then return nil, string.format( "%s not formated properly ($$HEADER$$ or $$FOOTER$$ not found)", fullname ) end -- Iterate through all the links in the document and change the local ones with -- the correct language option orig = orig:gsub( [==[]==], function( link ) if beginswith( link, "#" ) or beginswith( link, "http://" ) or beginswith( link, "https://" ) or beginswith( link, "ftp://" ) then return string.format( '', link ) else return string.format( '', lang, link ) end end ) -- Anticipate some common errors and fix them directly orig = orig:gsub( "
    ", "
    " ) orig = orig:gsub( '(
    )([^\n]-)%s-\n', function( anchor, data ) return anchor:gsub( ">", " />" ) .. data .. "\n" end ) orig = orig:gsub( '

    (.-)

    ', "
    %1
    " ) orig = orig:gsub( 'target="_blank"', "" ) -- Generate actual data local header = string.format( [=[ %s ]=], get_menu_title( item, lang ) ) header = header .. gen_logo( fname, lang ) .. "\n" local menuitems = gen_html_nav( parentid, lang ) header = header .. menuitems .. '
    \n' local footer = [[
    ]] orig = orig:gsub( "%$%$HEADER%$%$", header ) orig = orig:gsub( "%$%$FOOTER%$%$", footer ) return orig end ------------------------------------------------------------------------------- -- Documentation generator -- Helper function: iterate through the menu and replace automatically generated content local function replace_auto_content( automenus, item ) if type( item[ submenu_idx ] ) == "string" then local r = automenus[ item[ submenu_idx ] ] if not r then return string.format( "Autogenerated menu '%s' not found", item[ submenu_idx ] ) else print( string.format( "Replaced autogenerated menu '%s'", item[ submenu_idx ] ) ) item[ submenu_idx ] = r end elseif type( item[ submenu_idx ] ) == "table" then for i = 1, #item[ submenu_idx ] do replace_auto_content( automenus, item[ submenu_idx ][ i ] ) end end end -- Argument check local args = { ... } local destdir if #args ~= 1 then print "Using 'dist/' as the destination directory" destdir = "dist" else destdir = args[ 1 ] end -- Read the documentation data themenu, translations, fixed = dofile( "docdata.lua" ) if not themenu or not translations or not fixed then print "docdata.lua doesn't return the proper data, aborting." return end -- Add the content generated from eluadoc to our menu(s) print "Generating HTML documentation..." indent_print() local automenus, genfiles = eluadoc.gen_html_doc() if not automenus then return end regular_print() -- Replace content generated by gen_html_doc in the menu for i = 1, #themenu do local replerr = replace_auto_content( automenus, themenu[ i ] ) if replerr then print( replerr ) return end end print( "done" ) -- If the destination directory doesn't exist, create it -- If it exists, remove it local attr = lfs.attributes( destdir ) if not attr then if not lfs.mkdir( destdir ) then print( string.format( "Unable to create directory %s", destdir ) ) return end else if attr.mode ~= "directory" then print( string.format( "%s is not a directory", destdir ) ) return end for k in lfs.dir( destdir ) do if k ~= "." and k ~= ".." then rm_dir_rec( destdir ) lfs.mkdir( destdir ) break end end end print "\nProcessing HTML templates..." indent_print() flist = get_file_list() for _, lang in ipairs( languages ) do for fname, entry in pairs( flist ) do print( string.format( "Processing %s %s...", fname, entry.item[ name_idx ] and "" or "(hidden entry)" ) ) local res, err = gen_html_page( fname, lang ) if not res then print( "***" .. err ) else local g = io.open( string.format( "%s/%s_%s", destdir, lang, fname ), "wb" ) if not g then print( string.format( "Unable to open %s for writing", fname ) ) else g:write( res ) g:close() end end end end regular_print() print "done" -- Now copy the fixed content in the documentation directory print "\nCopying fixed content ..." indent_print() for _, v in ipairs( fixed ) do print( string.format( "Copying %s", v ) ) if v:sub( -1 ) == "/" then copy_dir( v, destdir ) else copy_file( v, destdir ) end end regular_print() print "done" -- And delete the files generated by eluadoc print "\nCleaning up files generated by eluadoc..." indent_print() for _, v in pairs( genfiles ) do print( string.format( "Deleting %s...", v ) ) os.remove( v ) end regular_print() print "done" print( string.format( "\nEnjoy your documentation in %s :)", destdir ) )