///////////////////////////////////////////////////////////////////////////////

// MQ4CPP - Message queuing for C++

// Copyright (C) 2004-2007  Riccardo Pompeo (Italy)

//

// This library is free software; you can redistribute it and/or

// modify it under the terms of the GNU Lesser General Public

// License as published by the Free Software Foundation; either

// version 2.1 of the License, or (at your option) any later version.

//

// This library is distributed in the hope that it will be useful,

// but WITHOUT ANY WARRANTY; without even the implied warranty of

// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU

// Lesser General Public License for more details.

//

// You should have received a copy of the GNU Lesser General Public

// License along with this library; if not, write to the Free Software

// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

//

// Directory management documentation link: 

// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/base/listing_the_files_in_a_directory.asp

//


#define SILENT

#include "Logger.h"

#include "FileSystem.h"


#ifndef WIN32

#include <stdlib.h>

#include <stdio.h>

#include <sys/types.h>

#include <sys/stat.h> 

#include <fcntl.h>

#include <regex.h>

#include <errno.h>

#include <string.h>

#define IOBUFSIZE 16384

#endif


FileSystemException::FileSystemException() 
{ 
#ifdef WIN32

    LPVOID lpMsgBuf;
    DWORD dw = GetLastError(); 

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, NULL );

	msg=(char*)lpMsgBuf;
    LocalFree(lpMsgBuf);
#else

	char aMsg[256];
	aMsg[0]=0;
	int err=errno;
	msg=strerror(err);
#endif

}

string Persistent::getName()
{
	TRACE("Persistent::getName - start")
	if(itsPath.size()==0)
		throw FileSystemException("Persistent::getName: path descriptor empty");

	string& aString=itsPath[itsPath.size()-1];
	TRACE("Persistent::getName - end")
	return aString;
}

void Persistent::setCreationTime(FSTimeType& theTime)
{
	TRACE("Persistent::setCreationTime - start")
#ifdef WIN32

	itsCreationTime.dwLowDateTime=theTime.dwLowDateTime;
	itsCreationTime.dwHighDateTime=theTime.dwHighDateTime;	
#else

	itsCreationTime=theTime;
#endif

	TRACE("Persistent::setCreationTime - end")
}

void Persistent::setSize(FSSizeType& theSize)
{
	TRACE("Persistent::setSize - start")
	itsSize.size_low=theSize.size_low;
	itsSize.size_high=theSize.size_high;
	TRACE("Persistent::setSize - end")
}

void Persistent::decodePath(const char* thePath)
{
	TRACE("Persistent::decodePath - start")
	TRACE("Path=" << thePath)
	itsPath.clear();
	char aBuffer[256];
	istrstream aStream(thePath);
	while(!aStream==false)
	{
		aStream.getline(aBuffer,sizeof(aBuffer),PATH_DELIM);
		if(!aStream==false)
		{
			if(aBuffer[0]!=0)
			{
				TRACE("Value=" << aBuffer)
				itsPath.push_back(aBuffer);
			}
		}
	}		
	TRACE("Persistent::decodePath - end")
}

string Persistent::encodeFullName()
{
	TRACE("Persistent::encodeFullName - start")
#ifdef WIN32 

	string res;
#else

	string res;
	res=PATH_DELIM;
#endif


	if(itsPath.size()>0)
	{
		unsigned cnt=1;
		for(vector<string>::iterator i = itsPath.begin(); i < itsPath.end(); ++i, ++cnt)
		{
			res+=*i;
			if(cnt<itsPath.size())
				res+=PATH_DELIM;
		}	
	}

	TRACE("Result=" << res.c_str())		
	TRACE("Persistent::encodeFullName - end")
	return res;	
}

string Persistent::encodePath()
{
	TRACE("Persistent::encodePath - start")
#ifdef WIN32 

	string res;
#else

	string res;
	res=PATH_DELIM;
#endif


	unsigned size=itsPath.size();
	if(size>0)
	{
		for(unsigned cnt=1; cnt<size; cnt++)
		{
			res+=itsPath[cnt-1];
			res+=PATH_DELIM;
		}	
	}

	TRACE("Result=" << res.c_str())		
	TRACE("Persistent::encodePath - end")
	return res;	
}

void Persistent::move(const char* theDestPath)
{
	TRACE("Persistent::move - start")
	string aSrcName=encodeFullName();
	string aDestName=theDestPath;
	aDestName+=PATH_DELIM;
	aDestName+=getName();
	TRACE("Source=" << aSrcName.c_str())
	TRACE("Destination=" << aDestName.c_str())

#ifdef WIN32

	BOOL ret=MoveFile(aSrcName.c_str(),aDestName.c_str());
	if(ret==FALSE)
		throw FileSystemException();
#else

	if(rename(aSrcName.c_str(),aDestName.c_str())<0)
		throw FileSystemException();
#endif	


	decodePath(aDestName.c_str());
	TRACE("Persistent::move - end")	
}

File::File(File* theObj) : Persistent("File") //++1.7

{
	TRACE("File::File - start")
	itsPath=theObj->itsPath;
	itsReadOnly=theObj->itsReadOnly;
	itsHidden=theObj->itsHidden;
	memcpy(&itsCreationTime,&theObj->itsCreationTime,sizeof(FSTimeType));
	memcpy(&itsSize,&theObj->itsSize,sizeof(FSSizeType));
	TRACE("File::File - end")
}

fstream& File::open()
{
	TRACE("File::open - start")
	string aFullName=encodeFullName();
	itsStream.open(aFullName.c_str(),fstream::in|fstream::binary);
	if(!itsStream)
		throw FileSystemException("Failed to open file");
		
	itsStream.seekg(0, ios::end);
	if(!itsStream)
		throw FileSystemException("Failed to seek file");

  	itsSize.size_low = itsStream.tellg();
	itsSize.size_high = 0L;

  	itsStream.seekg(0, ios::beg);	
	if(!itsStream)
		throw FileSystemException("Failed to seek file");
			
	TRACE("File::open - end")
	return itsStream;
}

fstream& File::create()
{
	TRACE("File::create - start")
	string aFullName=encodeFullName();
	itsStream.open(aFullName.c_str(),fstream::out|fstream::trunc|fstream::binary);
	if(!itsStream)
		throw FileSystemException("Failed to open file");	
	return itsStream;
	TRACE("File::create - end")
}

void File::close()
{
	TRACE("File::close - start")
	itsStream.close();
	TRACE("File::close - end")
}

File* File::copy(const char* theDestFullName)
{
	TRACE("File::copy - start")
	TRACE("Destination=" << theDestFullName)
	string aSrcName=encodeFullName();

#ifdef WIN32

	BOOL success=CopyFile(aSrcName.c_str(),theDestFullName,TRUE);
	if(success==FALSE)
		throw FileSystemException();
#else

	struct stat statbuf;
	int in_fd = ::open(aSrcName.c_str(), O_RDONLY);
	if(in_fd<0)
		throw FileSystemException();

	fstat(in_fd, &statbuf);

	int out_fd = ::open(theDestFullName, O_WRONLY|O_CREAT|O_TRUNC, statbuf.st_mode);
	if(out_fd<0)
	{
		::close(in_fd);
		throw FileSystemException();
	}

	char* aBuffer=new char[IOBUFSIZE];

	unsigned long bytesleft=statbuf.st_size;
	while(bytesleft>0)
	{
		ssize_t bytesread=read(in_fd, aBuffer, ((bytesleft>IOBUFSIZE)? IOBUFSIZE : bytesleft));
		if(bytesread==-1)
		{
			::close(out_fd);
			::close(in_fd);
			throw FileSystemException();
		}
	
		ssize_t bytewritten=write(out_fd, aBuffer, bytesread);
		if(bytewritten==-1 || bytewritten!=bytesread)
		{
			::close(out_fd);
			::close(in_fd);
			throw FileSystemException();
		}

		bytesleft-=bytewritten;
	}

	delete [] aBuffer;

	fsync(out_fd);
	::close(out_fd);
	::close(in_fd);
#endif	


	File* ret=new File(theDestFullName);
	ret->itsHidden=itsHidden;
	ret->itsReadOnly=itsReadOnly;
	ret->itsSize=itsSize;
	memcpy(&ret->itsCreationTime,&itsCreationTime,sizeof(FSTimeType));
	memcpy(&ret->itsSize,&itsSize,sizeof(FSSizeType));

	TRACE("File::copy - end")	
	return ret;
}

void File::remove()
{
	TRACE("File::remove - start")
	string aFullName=encodeFullName();
	
#ifdef WIN32	

	BOOL ret=DeleteFile(aFullName.c_str());
	if(ret==FALSE)
		throw FileSystemException();
#else

	if(::remove(aFullName.c_str())<0)
		throw FileSystemException();
#endif	


	TRACE("File::remove - end")
}	

Directory::Directory(Directory* theObj) : Persistent("Directory") //++1.7

{
	TRACE("Directory::Directory - start")
	itsPath=theObj->itsPath;
	itsReadOnly=theObj->itsReadOnly;
	itsHidden=theObj->itsHidden;
	memcpy(&itsCreationTime,&theObj->itsCreationTime,sizeof(FSTimeType));
	memcpy(&itsSize,&theObj->itsSize,sizeof(FSSizeType));
	TRACE("Directory::Directory - end")
}

Directory* Directory::upper() //++1.7

{
	TRACE("Directory::upper")
	return new Directory(encodePath());
}

Directory* Directory::getCurrent()
{
	TRACE("Directory::getCurrent - start")
#ifdef WIN32

	char buf[256];
	BOOL success=GetCurrentDirectory(sizeof(buf),buf);
	if(success==FALSE)
		throw FileSystemException();	
	TRACE("Current directory=" << buf)
	string path = replaceAll(string(buf), "\\", "/");
	Directory* ret=new Directory(path);
	ret->search();
#else

#ifdef __FreeBSD__

	char* ptr=getcwd (NULL, PATH_MAX);
#else

	char* ptr=get_current_dir_name();
#endif

	TRACE("Current directory=" << ptr)
	Directory* ret=new Directory(ptr);
	ret->search();
	::free(ptr);
#endif

	TRACE("Directory::getCurrent - end")
	return ret;
}

void Directory::free()
{
	TRACE("Directory::free - start")
	if(itsContent.size()>0)
	{
		for(vector<Persistent*>::iterator i = itsContent.begin(); i < itsContent.end(); ++i)
		{
			delete *i;
		}	
	}
	itsContent.clear();	
	TRACE("Directory::free - end")
}

string Directory::replaceAll(string strBase, string strOld, string strNew)
{
	TRACE("Directory::replaceAll - start")
	unsigned long iIndex1 = strBase.find(strOld, 0);
	unsigned long iIndex2 = 0;
	unsigned long iLengthOld = strOld.length();
	unsigned long iLengthNew = strNew.length();
	while (iIndex1 != string::npos)
	{
		iIndex2 = iIndex1 + iLengthNew + 1;
		strBase = strBase.erase(iIndex1, iLengthOld);
		strBase = strBase.insert(iIndex1, strNew);
		iIndex1 = strBase.find(strOld, iIndex2);
	}
	TRACE("Directory::replaceAll - end")
	return strBase;
}

void Directory::search(const char* thePattern) 
{
	TRACE("Directory::search - start")
	TRACE("Pattern=" << thePattern)

	free();
	string aCurPath=encodeFullName();

#ifdef WIN32	

	aCurPath+=PATH_DELIM;
	string aDirSpec=aCurPath;
	aDirSpec+=thePattern;
	TRACE("Search path=" << aDirSpec.c_str())

	if(aCurPath=="/") //++v1.7

	{
	   BOOL bFlag;
	   TCHAR Buf[MAX_PATH];          // temporary buffer for volume name

	   TCHAR Drive[] = TEXT("c:\\"); // template drive specifier

	   TCHAR I;                      // generic loop counter

	
	   for (I = TEXT('a'); I < TEXT('z');  I++ ) 
	   {
	      // Stamp the drive for the appropriate letter.

	      Drive[0] = I;
	
	      bFlag = GetVolumeNameForVolumeMountPoint(
	                 Drive,   // input volume mount point or directory

	                 Buf,     // output volume name buffer

	                 MAX_PATH // size of volume name buffer

	              );
	
	      if (bFlag) 
	      {
				TRACE("Found drive=" << Drive)
				string drv;
				drv+=(char)Drive[0];
				drv+=":";
				itsContent.push_back(new Directory(drv));
	      }
	   }
	}
	else
	{
		WIN32_FIND_DATA FindFileData;
		HANDLE hFind = INVALID_HANDLE_VALUE;
		hFind = FindFirstFile(aDirSpec.c_str(), &FindFileData);
	
		if (hFind == INVALID_HANDLE_VALUE) 
			throw FileSystemException();
	
		do 
		{
			TRACE("Current file name is " << FindFileData.cFileName)
			string aFileName=FindFileData.cFileName;
			string aFullName=aCurPath+aFileName;
	
			if(aFileName!="." && aFileName!="..") //++v1.7

			{ 
				Persistent* aPersistent=NULL;			 			
				if(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
					aPersistent=new Directory(aFullName);
				else
					aPersistent=new File(aFullName);
				
				if(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
					aPersistent->setReadOnly();
				
				if(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
					aPersistent->setHidden();
		
				if(FindFileData.ftCreationTime.dwLowDateTime!=0 && FindFileData.ftCreationTime.dwHighDateTime!=0)
					aPersistent->setCreationTime(FindFileData.ftCreationTime);
				else
					aPersistent->setCreationTime(FindFileData.ftLastAccessTime);
		
				FSSizeType aSize;
				aSize.size_low=FindFileData.nFileSizeLow;
				aSize.size_high=FindFileData.nFileSizeHigh;
				aPersistent->setSize(aSize);
		
				itsContent.push_back(aPersistent);
			}
		}
		while (FindNextFile(hFind, &FindFileData) != 0); 
		FindClose(hFind);
	}
#else

	string pattern = replaceAll(thePattern, ".", "\\.");
	pattern = replaceAll(pattern, "*", ".*");
	pattern = pattern + "$";
	TRACE("Pattern=" << pattern.c_str())

	regex_t regex;
	if (regcomp(&regex, pattern.c_str(), REG_EXTENDED|REG_NOSUB) == -1)
		throw FileSystemException();

	dirent* dp=NULL;
	DIR* dirp = opendir(aCurPath.c_str()); 
	if(dirp==NULL)
		throw FileSystemException();

	while ((dp = readdir(dirp))!= NULL)
	{
	    if (regexec(&regex, dp->d_name, 0, NULL, 0) == 0)
	    {
			TRACE("Current file name is " << dp->d_name)
			string aFileName=dp->d_name;
			string aFullName=aCurPath;
			aFullName+=PATH_DELIM;
			aFullName+=aFileName;
			TRACE("Full path name=" << aFullName.c_str())

			if(aFileName!="." && aFileName!="..") //++v1.7

			{ 
				Persistent* aPersistent=NULL;
				struct stat aStat;
				if(stat(aFullName.c_str(),&aStat)<0)
					throw FileSystemException();

				if(dp->d_type==DT_UNKNOWN)
				{
					// Fix reiserfs problem on kernel 2.6

					if((aStat.st_mode & S_IFDIR))
						aPersistent=new Directory(aFullName);
					else
						aPersistent=new File(aFullName);
				}
				else if(dp->d_type==DT_DIR)
					aPersistent=new Directory(aFullName);
				else
					aPersistent=new File(aFullName);
				
				if(!(aStat.st_mode & S_IWUSR))
					aPersistent->setReadOnly();
				
				if(dp->d_name[0]=='.')
					aPersistent->setHidden();
		
				aPersistent->setCreationTime(aStat.st_mtime);
		
				FSSizeType aSize;
				aSize.size_low=aStat.st_size;
				aSize.size_high=0;
				aPersistent->setSize(aSize);
		
				itsContent.push_back(aPersistent);
			}
	    }
	}
	closedir(dirp);
  	regfree(&regex);	
#endif


	TRACE("Directory::search - end")
}

Persistent* Directory::get(const char* theName)
{
	TRACE("Directory::get - start")
	TRACE("Searching for " << theName)
	Persistent* res=NULL;
	if(itsContent.size()>0)
	{
		for(vector<Persistent*>::iterator i = itsContent.begin(); i < itsContent.end(); ++i)
		{
			Persistent* anObj=*i;
			if(anObj->getName()==theName)
			{
				res=anObj;
				break;
			}
		}	
	}
	TRACE("Directory::get - end")
	return res;
}

Persistent* Directory::get(unsigned thePosition) // ++v1.7

{
	TRACE("Directory::get - start")
	TRACE("Position=" << thePosition)
	Persistent* res=NULL;
	if(thePosition < itsContent.size())
	{
		res=itsContent[thePosition];
	}
	TRACE("Directory::get - end")
	return res;
}

vector<Persistent*>::iterator Directory::getIterator()
{
	return itsContent.begin();
}

bool Directory::testIterator(vector<Persistent*>::iterator& i)
{
	return (i < itsContent.end());
}

void Directory::find(vector<File*>& theResult,Directory& theDir,const char* thePattern)
{
	TRACE("Directory::find - start")
	theDir.search(thePattern);
	for(vector<Persistent*>::iterator i = theDir.getIterator(); theDir.testIterator(i) ; ++i)
	{
		Persistent* anObj=*i;		
		if(anObj->is("File"))
		{
			theResult.push_back((File*)anObj);
		}
		else if(anObj->is("Directory"))
		{
			find(theResult,(Directory&)*anObj,thePattern);
		}
	}
	TRACE("Directory::find - end")	
}

void Directory::copy(Directory& theDestDir,Directory& theSrcDir)
{
	TRACE("Directory::copy - start")
	theSrcDir.search();
	for(vector<Persistent*>::iterator i = theSrcDir.getIterator(); theSrcDir.testIterator(i) ; ++i)
	{
		Persistent* anObj=*i;		
		if(anObj->is("File"))
		{
			theDestDir.copy((File*)anObj);
		}
		else if(anObj->is("Directory"))
		{
			string aName=theSrcDir.getName();
			Directory* aDir=theDestDir.mkdir(aName.c_str());
			copy(*aDir,(Directory&)*anObj);
		}
	}
	TRACE("Directory::copy - end")		
}

void Directory::rmdir()
{
	TRACE("Directory::rmdir - start")
	string aFullName=encodeFullName();

#ifdef WIN32		

	BOOL ret=RemoveDirectory(aFullName.c_str());
	if(ret==FALSE)
		throw FileSystemException();	
#else

	if(::rmdir(aFullName.c_str())<0)
		throw FileSystemException();
#endif


	TRACE("Directory::rmdir - end")
}

void Directory::rmdir(Directory& theDir)
{
	TRACE("Directory::rmdir - start")
	theDir.search();
	for(vector<Persistent*>::iterator i = theDir.getIterator(); theDir.testIterator(i) ; ++i)
	{
		Persistent* anObj=*i;		
		if(anObj->is("File"))
		{
			((File*)anObj)->remove();
		}
		else if(anObj->is("Directory"))
		{
			rmdir((Directory&)*anObj);
		}
	}
	theDir.rmdir();
	TRACE("Directory::rmdir - end")	
}

void Directory::move(Directory& theDestDir,Directory& theSrcDir)
{
	TRACE("Directory::move - start")
	theSrcDir.search();
	for(vector<Persistent*>::iterator i = theSrcDir.getIterator(); theSrcDir.testIterator(i) ; ++i)
	{
		Persistent* anObj=*i;		
		if(anObj->is("File"))
		{
			theDestDir.copy((File*)anObj);
			((File*)anObj)->remove();
		}
		else if(anObj->is("Directory"))
		{
			string aName=theSrcDir.getName();
			Directory* aDir=theDestDir.mkdir(aName.c_str());
			move(*aDir,(Directory&)*anObj);
		}
	}
	theSrcDir.rmdir();
	TRACE("Directory::move - end")		
}

Directory* Directory::mkdir(const char* theName)
{
	TRACE("Directory::mkdir - start")
	string aFullName=encodeFullName();
	aFullName+=PATH_DELIM;
	aFullName+=theName;

#ifdef WIN32		

	BOOL ret=CreateDirectory(aFullName.c_str(),NULL);
	if(ret==FALSE)
		throw FileSystemException();
#else

	if(::mkdir(aFullName.c_str(),S_IWUSR|S_IRUSR|S_IXUSR|S_IWGRP|S_IRGRP|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)<0)
		throw FileSystemException();
#endif		


	Directory* aDir=new Directory(aFullName);
	itsContent.push_back(aDir);
	TRACE("Directory::mkdir - end")
	return aDir;		
}

Directory* Directory::mkfulldir(const char* theFullPath)
{
	TRACE("Directory::mkfulldir - start")
	TRACE("Path=" << theFullPath)
	char aBuffer[256];
	Directory* aDir=NULL;
	Directory* aRoot=NULL;
	Directory* ret=NULL;
	istrstream aStream(theFullPath);

	while(!aStream==false)
	{
		aStream.getline(aBuffer,sizeof(aBuffer),PATH_DELIM);
		if(!aStream==false && aBuffer[0]!=0)
		{
			if(aDir==NULL)
			{
				aRoot=new Directory(aBuffer);
				aDir=aRoot;
			}
			else
			{
				aDir->search();
				Persistent* aPersistent=aDir->get(aBuffer);
				if(aPersistent!=NULL && aPersistent->is("Directory"))
				{
					TRACE("Found dir=" << aBuffer)
					aDir=(Directory*)aPersistent;
				}
				else
				{
					TRACE("Create new dir=" << aBuffer)
					aDir=aDir->mkdir(aBuffer);
				}
			}
		}
	}
	
	if(aDir!=NULL)
		ret=new Directory(aDir);
	else
		throw FileSystemException();

	if(aRoot!=NULL)
		delete aRoot;
			
	TRACE("Directory::mkfulldir - end")
	return ret;
}

File* Directory::create(const char* theFileName)
{
	TRACE("Directory::create - start")
	string aFullName=encodeFullName();
	aFullName+=PATH_DELIM;
	aFullName+=theFileName;
	File* aFile=new File(aFullName.c_str());
	itsContent.push_back(aFile);
	TRACE("Directory::create - end")
	return aFile;
}

void Directory::copy(File* theFile)
{
	TRACE("Directory::copy - start")
	string aFullName=encodeFullName();
	aFullName+=PATH_DELIM;
	aFullName+=theFile->getName();
	File* aFile=theFile->copy(aFullName.c_str());
	itsContent.push_back(aFile);
	TRACE("Directory::copy - end")		
}

void testFS()
{
	char* TMPDIR="/tmp";
	char* TLOGDIR="/tmp/tlog/test_copy.log";
	char* TLOG1DIR="/tmp/tlog1";

	DISPLAY("Start test")
	Directory* aTlogDir=NULL;
	Directory* aTlogDir1=NULL;
	
	Directory* aCurDir=Directory::getCurrent();
	Directory* aTmpDir=new Directory(TMPDIR);
	aTmpDir->search();

	aTlogDir=(Directory*)aTmpDir->get("tlog");
	if(aTlogDir==NULL)
	{
		aTlogDir=aTmpDir->mkdir("tlog");
		DISPLAY(aTlogDir->getName() << " created")		
	}	

	aTlogDir1=(Directory*)aTmpDir->get("tlog1");
	if(aTlogDir1==NULL)
	{
		aTlogDir1=aTmpDir->mkdir("tlog1");		
		DISPLAY(aTlogDir1->getName() << " created")		
	}	

	File* aFile=((Directory*)aTlogDir)->create("test.log");
	fstream& aStream=aFile->create();
	aStream << "Hello World!";
	aFile->close();
	DISPLAY(aFile->getName() << " created")		

	aFile->move(TLOG1DIR);
	File* aFile1=aFile->copy(TLOGDIR);
	DISPLAY(aFile1->getName() << " copied")		

	aFile->remove();	
	aFile1->remove();
	aTlogDir->rmdir();
	aTlogDir1->rmdir();
	
	delete aCurDir;
	delete aTmpDir;	
	DISPLAY("Test done")		
}





syntax highlighted by Code2HTML, v. 0.9.1