You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdesdk/umbrello/umbrello/codegenerators/rubywriter.cpp

449 lines
14 KiB

/***************************************************************************
rubywriter.h - description
-------------------
begin : Sat Dec 21 2002
copyright : Vincent Decorges
email : vincent.decorges@eivd.ch
(C) 2003-2006 Umbrello UML Modeller Authors <uml-devel@uml.sf.net>
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "rubywriter.h"
#include <kdebug.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <tqfile.h>
#include <tqtextstream.h>
#include <tqregexp.h>
#include "classifierinfo.h"
#include "../umldoc.h"
#include "../umlattributelist.h"
#include "../association.h"
#include "../attribute.h"
#include "../classifier.h"
#include "../operation.h"
#include "../umlnamespace.h"
RubyWriter::RubyWriter() {
}
RubyWriter::~RubyWriter() {}
void RubyWriter::writeClass(UMLClassifier *c) {
if(!c) {
kDebug()<<"Cannot write class of NULL concept!" << endl;
return;
}
TQString classname = cleanName(c->getName());
UMLClassifierList superclasses = c->getSuperClasses();
UMLAssociationList aggregations = c->getAggregations();
UMLAssociationList compositions = c->getCompositions();
//find an appropriate name for our file
TQString fileName = findFileName(c, ".rb");
if (fileName.isEmpty()) {
emit codeGenerated(c, false);
return;
}
TQFile fileh;
if( !openFile(fileh, fileName) ) {
emit codeGenerated(c, false);
return;
}
TQTextStream h(&fileh);
// preparations
classifierInfo = new ClassifierInfo(c);
classifierInfo->fileName = fileName;
classifierInfo->className = cleanName(c->getName());
//////////////////////////////
//Start generating the code!!
/////////////////////////////
//try to find a heading file (license, coments, etc)
TQString str;
str = getHeadingFile(".rb");
if(!str.isEmpty()) {
str.replace(TQRegExp("%filename%"), fileName);
str.replace(TQRegExp("%filepath%"), fileh.name());
h<<str<<m_endl;
}
if(forceDoc() || !c->getDoc().isEmpty()) {
TQString docStr = c->getDoc();
docStr.replace(TQRegExp("\\n"), "\n# ");
docStr.replace("@ref ", "");
docStr.replace("@see", "_See_");
docStr.replace("@short", "_Summary_");
docStr.replace("@author", "_Author_");
h<<"#"<<m_endl;
h<<"# "<<docStr<<m_endl;
h<<"#"<<m_endl<<m_endl;
}
// write inheritances out
UMLClassifier *concept;
h<< "class " << cppToRubyType(classname) << (superclasses.count() > 0 ? " < ":"");
int i = 0;
for (concept = superclasses.first(); concept; concept = superclasses.next()) {
if (i == 0) {
h << cppToRubyType(concept->getName()) << m_endl;
} else {
// Assume ruby modules that can be mixed in, after the first
// superclass name in the list
h << m_indentation << "include "<< cppToRubyType(concept->getName()) << m_endl;
}
i++;
}
h << m_endl;
// write comment for sub-section IF needed
if (forceDoc() || classifierInfo->hasAccessorMethods) {
h << m_indentation << "#" << m_endl;
h << m_indentation << "# Accessor Methods" << m_endl;
h << m_indentation << "#" << m_endl << m_endl;
// Accessor methods for attributes
writeAttributeMethods(&(classifierInfo->atpub), Uml::Visibility::Public, h);
writeAttributeMethods(&(classifierInfo->atprot), Uml::Visibility::Protected, h);
writeAttributeMethods(&(classifierInfo->atpriv), Uml::Visibility::Private, h);
h << m_endl;
}
//operations
writeOperations(c, h);
//finish files
h << "end" << m_endl << m_endl;
//close files and notfiy we are done
fileh.close();
emit codeGenerated(c, true);
}
////////////////////////////////////////////////////////////////////////////////////
// Helper Methods
TQString RubyWriter::cppToRubyType(const TQString &typeStr) {
TQString type = cleanName(typeStr);
type.replace("const ", "");
type.replace(TQRegExp("[*&\\s]"), "");
type.replace(TQRegExp("[<>]"), "_");
type.replace("TQStringList", "Array");
type.replace("TQString", "String");
type.replace("bool", "true|false");
type.replace(TQRegExp("^(uint|int|ushort|short|ulong|long)$"), "Integer");
type.replace(TQRegExp("^(float|double)$"), "Float");
type.replace(TQRegExp("^Q(?=[A-Z])"), "TQt::");
type.replace(TQRegExp("^K(?!(DE|Parts|IO)"), "KDE::");
return type;
}
TQString RubyWriter::cppToRubyName(const TQString &nameStr) {
TQString name = cleanName(nameStr);
name.replace(TQRegExp("^m_"), "");
name.replace(TQRegExp("^[pbn](?=[A-Z])"), "");
name = name.mid(0, 1).lower() + name.mid(1);
return name;
}
void RubyWriter::writeOperations(UMLClassifier *c,TQTextStream &h) {
//Lists to store operations sorted by scope
UMLOperationList oppub,opprot,oppriv;
oppub.setAutoDelete(false);
opprot.setAutoDelete(false);
oppriv.setAutoDelete(false);
//sort operations by scope first and see if there are abstract methods
UMLOperationList opl(c->getOpList());
for(UMLOperation *op = opl.first(); op ; op = opl.next()) {
switch(op->getVisibility()) {
case Uml::Visibility::Public:
oppub.append(op);
break;
case Uml::Visibility::Protected:
opprot.append(op);
break;
case Uml::Visibility::Private:
oppriv.append(op);
break;
default:
break;
}
}
TQString classname(cleanName(c->getName()));
//write operations to file
if(forceSections() || !oppub.isEmpty()) {
writeOperations(classname, oppub, Uml::Visibility::Public, h);
}
if(forceSections() || !opprot.isEmpty()) {
writeOperations(classname, opprot, Uml::Visibility::Protected, h);
}
if(forceSections() || !oppriv.isEmpty()) {
writeOperations(classname, oppriv, Uml::Visibility::Private, h);
}
}
void RubyWriter::writeOperations(const TQString &classname, UMLOperationList &opList,
Uml::Visibility permitScope, TQTextStream &h)
{
UMLOperation *op;
UMLAttribute *at;
switch (permitScope) {
case Uml::Visibility::Public:
h << m_indentation << "public" << m_endl << m_endl;
break;
case Uml::Visibility::Protected:
h << m_indentation << "protected" << m_endl << m_endl;
break;
case Uml::Visibility::Private:
h << m_indentation << "private" << m_endl << m_endl;
break;
default:
break;
}
for (op=opList.first(); op ; op=opList.next()) {
TQString methodName = cleanName(op->getName());
TQStringList commentedParams;
// Skip destructors, and operator methods which
// can't be defined in ruby
if ( methodName.startsWith("~")
|| methodName == "operator ="
|| methodName == "operator --"
|| methodName == "operator ++"
|| methodName == "operator !=" )
{
continue;
}
if (methodName == classname) {
methodName = "initialize";
}
methodName.replace("operator ", "");
methodName = methodName.mid(0, 1).lower() + methodName.mid(1);
UMLAttributeList atl = op->getParmList();
//write method doc if we have doc || if at least one of the params has doc
bool writeDoc = forceDoc() || !op->getDoc().isEmpty();
// Always write out the docs for ruby as the type of the
// arguments and return value of the methods is useful
writeDoc = true;
// for (at = atl.first(); at; at = atl.next())
// writeDoc |= !at->getDoc().isEmpty();
if (writeDoc) {
h << m_indentation << "#" << m_endl;
TQString docStr = op->getDoc();
docStr.replace(TQRegExp("[\\n\\r]+ *"), m_endl);
docStr.replace(TQRegExp("[\\n\\r]+\\t*"), m_endl);
docStr.replace(" m_", " ");
docStr.replace(TQRegExp("\\s[npb](?=[A-Z])"), " ");
TQRegExp re_params("@param (\\w)(\\w*)");
int pos = re_params.search(docStr);
while (pos != -1) {
docStr.replace( re_params.cap(0),
TQString("@param _") + re_params.cap(1).lower() + re_params.cap(2) + '_' );
commentedParams.append(re_params.cap(1).lower() + re_params.cap(2));
pos += re_params.matchedLength() + 3;
pos = re_params.search(docStr, pos);
}
docStr.replace("\n", TQString("\n") + m_indentation + "# ");
// Write parameter documentation
for (at = atl.first(); at ; at = atl.next()) {
// Only write an individual @param entry if one hasn't been found already
// in the main doc comment
if (commentedParams.contains(cppToRubyName(at->getName())) == 0) {
docStr += (m_endl + m_indentation + "# @param _" + cppToRubyName(at->getName()) + '_');
if (at->getDoc().isEmpty()) {
docStr += (' ' + cppToRubyType(at->getTypeName()));
} else {
docStr += (' ' + at->getDoc().replace(TQRegExp("[\\n\\r]+[\\t ]*"), m_endl + " "));
}
}
}
docStr.replace("@ref ", "");
docStr.replace("@param", "*");
docStr.replace("@return", "* _returns_");
// All lines after the first '# *' in the doc comment
// must be indented correctly. If they aren't a list
// item starting with '# *', then indent the text with
// three spaces, '# ', to line up with the list item.
pos = docStr.find("# *");
TQRegExp re_linestart("# (?!\\*)");
pos = re_linestart.search(docStr, pos);
while (pos > 0) {
docStr.insert(pos + 1, " ");
pos += re_linestart.matchedLength() + 2;
pos = re_linestart.search(docStr, pos);
}
h << m_indentation << "# "<< docStr << m_endl;
TQString typeStr = cppToRubyType(op->getTypeName());
if (!typeStr.isEmpty() && typeStr != "void" && docStr.contains("_returns_") == 0) {
h << m_indentation << "# * _returns_ " << typeStr << m_endl;
}
}
h<< m_indentation << "def " + methodName << "(";
int j=0;
for (at = atl.first(); at; at = atl.next(), j++) {
TQString nameStr = cppToRubyName(at->getName());
if (j > 0) {
h << ", " << nameStr;
} else {
h << nameStr;
}
h << (!(at->getInitialValue().isEmpty()) ?
(TQString(" = ") + cppToRubyType(at->getInitialValue())) :
TQString(""));
}
h <<")" << m_endl;
h << m_indentation << m_indentation << m_endl;
h << m_indentation << "end" << m_endl << m_endl;
}//end for
}
// this is for writing file attribute methods
//
void RubyWriter::writeAttributeMethods(UMLAttributeList *attribs,
Uml::Visibility visibility, TQTextStream &stream)
{
// return now if NO attributes to work on
if (attribs->count() == 0 || visibility == Uml::Visibility::Private)
return;
UMLAttribute *at;
for(at=attribs->first(); at; at=attribs->next())
{
TQString varName = cppToRubyName(cleanName(at->getName()));
writeSingleAttributeAccessorMethods(varName, at->getDoc(), stream);
}
}
void RubyWriter::writeSingleAttributeAccessorMethods(
const TQString &fieldName,
const TQString &descr,
TQTextStream &h)
{
TQString description = descr;
description.replace(TQRegExp("m_[npb](?=[A-Z])"), "");
description.replace("m_", "");
description.replace("\n", TQString("\n") + m_indentation + "# ");
if (!description.isEmpty()) {
h << m_indentation << "# " << description << m_endl;
}
h << m_indentation << "attr_accessor :" << fieldName << m_endl << m_endl;
return;
}
/**
* returns "Ruby"
*/
Uml::Programming_Language RubyWriter::getLanguage() {
return Uml::pl_Ruby;
}
const TQStringList RubyWriter::reservedKeywords() const {
static TQStringList keywords;
if (keywords.isEmpty()) {
keywords << "__FILE__"
<< "__LINE__"
<< "BEGIN"
<< "END"
<< "alias"
<< "and"
<< "begin"
<< "break"
<< "case"
<< "class"
<< "def"
<< "defined?"
<< "do"
<< "else"
<< "elsif"
<< "end"
<< "ensure"
<< "false"
<< "for"
<< "if"
<< "in"
<< "module"
<< "next"
<< "nil"
<< "not"
<< "or"
<< "redo"
<< "rescue"
<< "retry"
<< "return"
<< "self"
<< "super"
<< "then"
<< "true"
<< "undef"
<< "unless"
<< "until"
<< "when"
<< "while"
<< "yield";
}
return keywords;
}
#include "rubywriter.moc"