1
0
mirror of https://github.com/elua/elua.git synced 2025-01-08 20:56:17 +08:00
elua/doc/buildall.lua
2011-02-02 17:52:12 +00:00

737 lines
25 KiB
Lua

require "lfs"
require "eluadoc"
require "md5"
-- 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!
-- ## Obs: PT going offline in July 2010 for lack of support.
-- We hope to offer it again and I'll keep maintaining (offline) what I can.
--languages = { "en", "pt" }
languages = { "en" }
-- Reverse lookup (language to idx) dictionary
local langidx = {}
for k, v in ipairs( languages ) do
langidx[ v ] = k
end
local sf = string.format
local cache_invalid = false
-------------------------------------------------------------------------------
-- 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" and f ~= ".git" 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
-------------------------------------------------------------------------------
-- Cache helpers
local function read_md5( filename )
local fullname = string.format( "cache/%s.cache", filename )
local f = io.open( fullname, "rb" )
if not f then return "" end
local d = f:read( "*a" )
f:close()
return d
end
local function write_md5( filename, d )
local fullname = string.format( "cache/%s.cache", filename )
local f = io.open( fullname, "wb" )
if not f then return false end
f:write( d )
f:close()
return true
end
local function file_md5( filename )
local f = io.open( filename, "rb" )
if not f then return "" end
local d = f:read( "*a" )
f:close()
return md5.sumhexa( d )
end
-------------------------------------------------------------------------------
-- Table utils (from http://lua-users.org/wiki/TableUtils)
function table.val_to_str( v )
if "string" == type( v ) then
v = string.gsub( v, "\n", "\\n" )
if string.match( string.gsub(v,"[^'\"]",""), '^"+$' ) then
return "'" .. v .. "'"
end
return '"' .. string.gsub(v,'"', '\\"' ) .. '"'
else
return "table" == type( v ) and table.tostring( v ) or tostring( v )
end
end
function table.key_to_str ( k )
if "string" == type( k ) and string.match( k, "^[_%a][_%a%d]*$" ) then
return k
else
return "[" .. table.val_to_str( k ) .. "]"
end
end
function table.tostring( tbl )
local result, done = {}, {}
for k, v in ipairs( tbl ) do
table.insert( result, table.val_to_str( v ) )
done[ k ] = true
end
for k, v in pairs( tbl ) do
if not done[ k ] then
table.insert( result,
table.key_to_str( k ) .. "=" .. table.val_to_str( v ) )
end
end
return "{" .. table.concat( result, "," ) .. "}"
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
-- Links that begin with "http(s)://" are unchanged
local function get_link( lang, link )
if link == "#" then
return "#"
elseif link:find( "https?://" ) == 1 then
return link
else
return string.format( "%s_%s", lang, link )
end
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 = '<img class="rightarrowpointer" src="ddlevelsfiles/arrow-right.gif" alt="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( '<li><a href="%s">%s%s</a>\n', get_link( lang, l[ link_idx ] ), arrptr, get_menu_name( l, lang ) )
data = data .. string.rep( " ", level * 2 + 8 ) .. "<ul>\n"
data = data .. gen_submenus( l[ submenu_idx ], lang, level + 1 )
data = data .. string.rep( " ", level * 2 + 8 ) .. "</ul></li>\n"
else
if get_menu_name( l, lang ) then
data = data .. string.rep( " ", level * 2 + 8 ) .. string.format( '<li><a href="%s">%s</a></li>\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 = [[
<div id="nav">
<ul id="menu">
]]
local lidx = langidx[ lang ]
for i = 1, #themenu do
local menudata = ""
local imginsert = ""
local styledef = i == #themenu and ' style="border-bottom-width: 0"' or ""
local link = themenu[ i ][ link_idx ]
local name = get_menu_name( themenu[ i ], lang )
if not link then
htmlstr = htmlstr .. string.format(' <li class="sep"%s>%s</li>\n', styledef, name )
else
local relname = string.gsub( string.gsub( string.format( "s_%d_%s", i, string.lower( get_base_link( link ) ) ), "%s", "_" ), "%.html", "" )
-- If we have a submenu, update the HTML menu content part
if themenu[ i ][ submenu_idx ] then
menudata = string.format( [[
<ul id="%s">
%s
</ul>
]], relname, string.sub( gen_submenus( themenu[ i ][ submenu_idx ], lang ), 1, -2 ) )
imginsert = '<img class="rightarrowpointer" src="ddlevelsfiles/arrow-right.gif" alt="right arrow" />'
end
if name then
if i == parentid then
-- If this is the parent, use a special style for it (<a class="current"> or <li class="current">, depending on the item type)
if themenu[ i ][ submenu_idx ] then
htmlstr = htmlstr .. string.format(' <li><a class="current" href="%s" rel="%s"%s>%s%s</a>\n%s </li>\n', get_link( lang, link ), relname, styledef, imginsert, name, menudata )
else
htmlstr = htmlstr .. string.format(' <li class="current"%s>%s%s\n%s </li>\n', styledef, imginsert, name, menudata )
end
else
local submenustr = themenu[ i ][ submenu_idx ] and string.format( ' rel="%s"', relname ) or ""
htmlstr = htmlstr .. string.format(' <li><a href="%s"%s%s>%s%s</a>\n%s </li>\n', get_link( lang, link ), submenustr, styledef, imginsert, name, menudata )
end
end
end
end
offline_data = not is_offline and [[
<p style="margin-left: 35px;"><a href="http://www.pax.com/free-counters.html"><img src="http://counter.pax.com/counter/image?counter=ctr-zsg80nnmqt" alt="Free Hit Counter" style="border: 0;" /></a></p>
<p style="margin-left: 18px;"><a href="http://developer.berlios.de" title="BerliOS Developer"> <img src="http://developer.berlios.de/bslogo.php?group_id=9919" width="124px" height="32px" style="border: 0;" alt="BerliOS Developer Logo" /></a></p>
]] or ""
htmlstr = htmlstr .. string.format( [[
</ul>
%s</div>
]], offline_data )
return htmlstr
end
-- Helper function: replace local links with links prefixed by language
local function language_for_links( lang, orig )
-- Iterate through all the links in the document and change the local ones with
-- the correct language option
orig = orig:gsub( [==[<a href=["'](.-)["']>]==], function( link )
if beginswith( link, "#" ) or beginswith( link, "http://" ) or beginswith( link, "https://" ) or beginswith( link, "ftp://" ) then
return string.format( '<a href="%s">', link )
else
return string.format( '<a href="%s_%s">', lang, link )
end
end )
return orig
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(' <td align="center"><h6 class="selected"><img src="images/%s.jpg" alt="%s" style="border: 0;" /></h6></td>\n', hlang, crtlang )
else
langdata = langdata .. string.format(' <td align="center"><h6><a href="%s_%s" class="lang"><img src="images/%s.jpg" alt="%s" style="border: 0;" /></a></h6></td>\n', crtlang:lower(), fname, hlang, crtlang )
end
end
return string.format( [[
<form method="get" action="http://www.google.com/search">
<div id="logo">
<table border="0" style="width: 100%%%%;" cellspacing="0" cellpadding="0">
<tr>
<td valign="middle" style="width: 90px;"><img src="images/eLuaLogo.png" alt="eLua logo" class="logo_elua" /></td>
<td class="header_title" valign="middle">%s</td>
<td style="width: 280px;" align="center" valign="middle">
<table border="0" cellspacing="0" cellpadding="0">
<tr style="height: 40px;">
<td colspan="%d">
<input type="hidden" name="ie" value="utf-8" />
<input type="hidden" name="oe" value="utf-8" />
<input type="text" name="q" size="21" maxlength="255" value="" />
</td>
<td style="padding-left: 5px;">
<input type="submit" name="btnG" value="%s" style="height: 21px; font-size: x-small;" />
<input type="hidden" name="domains" value="http://www.eluaproject.net" />
<input type="hidden" name="sitesearch" value="http://www.eluaproject.net" />
</td>
</tr>
<tr>
<td align="center"><h6>%s:</h6></td>
%s
<td align="center">&nbsp;</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
</form>
]], 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
fullname = fullname:gsub( "%.html", "%.txt" )
f = io.open( fullname, "rb" )
if not f then
return nil, string.format( "Error opening %s", fullname )
end
end
local orig = f:read( "*a" )
f:close()
-- Check cache
local cfilename = string.format( "%s_%s", lang, fname )
local oldsum = read_md5( cfilename )
local crtsum = md5.sumhexa( orig )
if oldsum == crtsum then
if not cache_invalid then
return nil, "#cached#"
end
else
write_md5( cfilename, crtsum )
end
local asciimode = fullname:find( "%.txt" )
-- 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
if not asciimode then
print ""
-- Anticipate some common errors and fix them directly
orig = orig:gsub( "<br>", "<br />" )
orig = orig:gsub( '(<a name=["\'][^\'"]-["\']>)([^\n]-)</a>%s-\n', function( anchor, data )
return anchor:gsub( ">", " />" ) .. data .. "\n"
end )
orig = orig:gsub( '<p><pre><code>(.-)</code></pre></p>', "<pre><code>%1</code></pre>" )
orig = orig:gsub( 'target="_blank"', "" )
else
print( "(AsciiDoc mode)" )
-- Call "asciidoc" to generate the actual HTML
local tempname = fullname .. '.temp'
os.execute( sf( "asciidoc -s -a icons -a 'newline=\\n' -b xhtml11 -o %s %s", tempname, fullname ) )
local resfile = io.open( tempname, "rb" )
if not resfile then
return nil, sf( "Unable to find the AsciiDoc generated file %s", tempname )
end
orig = resfile:read( "*a" )
resfile:close()
orig = "$$HEADER$$\n" .. orig .. "$$FOOTER$$\n"
os.remove( tempname )
end
-- Replace local links with language-dependent links
orig = language_for_links( lang, orig )
-- Generate actual data
local header = string.format( [=[
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>%s</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="subject" content="eLua - Lua for the embedded world" />
<meta name="Description" content="eLua stands for Embedded Lua and the project aims to offer the full set of features of the Lua Programming Language to the embedded world." />
<meta name="Keywords" content="eLua, lua, embedded, ARM, Cortex-M3, AVR32, ARM7TDMI, microcontroller, mcu, programming, electronics, tools, development" />
<link href="menu.css" rel="stylesheet" type="text/css" />
<link href="style1.css" rel="stylesheet" type="text/css" />
<link REL="SHORTCUT ICON" HREF="images/eLua_16x16.ico">
<script type="text/javascript"><!--//--><![CDATA[//><!--
sfHover = function() {
var sfEls = document.getElementById("nav").getElementsByTagName("LI");
for (var i=0; i<sfEls.length; i++) {
sfEls[i].onmouseover=function() {
this.className+=" sfhover";
}
sfEls[i].onmouseout=function() {
this.className=this.className.replace(new RegExp(" sfhover\\b"), "");
}
}
}
if (window.attachEvent) window.attachEvent("onload", sfHover);
//--><!]]></script>
</head>
<body>
]=], get_menu_title( item, lang ) )
header = header .. gen_logo( fname, lang ) .. "\n"
local menuitems = gen_html_nav( parentid, lang )
header = header .. menuitems .. '<div id="content">\n' .. ( asciimode and "" or '<div class="sectionbody">' )
local footer = ( asciimode and "" or '</div>' ) .. [[
</div>
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%%3E%%3C/script%%3E"));
</script>
<script type="text/javascript">
try {
var pageTracker = _gat._getTracker("UA-11834941-1");
pageTracker._trackPageview();
} catch(err) {}</script>
</body>
</html>
]]
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 = "dist"
local destdiridx = 1
if #args > 2 then
print "Usage: buildall.lua [destdir] [-online] [-clean]"
print "Use -online to generate online documentation (includes BerliOS logo and counter)"
print "Use -clean to clear the cache and generate clean documentation"
return
end
local cleancache = false
for i = 1, #args do
if args[ i ] == "-online" then
is_offline = false
elseif args[ i ] == "-clean" then
cleancache = true
else
destdir = args[ i ]
end
end
print( sf( "Using '%s' as the destination directory", destdir ) );
-- 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
rm_dir_rec( destdir )
lfs.mkdir( destdir )
end
-- If the cache must be cleared, do it now
if cleancache then
local attr = lfs.attributes( 'cache' )
if attr then
if attr.mode ~= "directory" then
print( "'cache' is not a directory" )
return
end
rm_dir_rec( 'cache' )
lfs.mkdir( 'cache' )
end
end
-- Create the cache directory if it doesn't exist
local attr = lfs.attributes( 'cache' )
if not attr then
if not lfs.mkdir( 'cache' ) then
print( "Unable to create cache directory" )
return
end
end
-- Set the global "cache invalid" flag
-- It is set to 'true' if the content of docdata.lua changes
local crtdocsum = md5.sumhexa( table.tostring( themenu ) )
local oldsum = read_md5( "docdata" )
cache_invalid = crtdocsum ~= oldsum
if cache_invalid then
write_md5( "docdata", crtdocsum )
print "Cache invalidated"
end
print "\nProcessing HTML templates..."
indent_print()
flist = get_file_list()
for _, lang in ipairs( languages ) do
for fname, entry in pairs( flist ) do
if fname:find( "https?://" ) ~= 1 then -- not a filename but a direct link
io.write( string.format( "Processing %s %s...", fname, entry.item[ name_idx ] and "" or "(hidden entry)" ) )
local res, err = gen_html_page( fname, lang )
if err == "#cached#" then
-- This file is already in the cache
print( " (cached)" )
elseif not res then
print( "***" .. err )
else
local g = io.open( string.format( "cache/%s_%s", 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
-- Copy file from cache to destination directory
local srcf = io.open( string.format( "cache/%s_%s", lang, fname ), "rb" )
local destf = io.open( string.format( "%s/%s_%s", destdir, lang, fname ), "wb" )
if not srcf or not destf then
print "Unable to copy file from cache to dist"
return
end
local content = srcf:read( "*a" )
destf:write( content )
srcf:close()
destf:close()
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 ) )