一、前言
IronRuby是.NET下的一個Ruby實現,此外還有Ruby.net這一開源項目,二者的主要區別是IronRuby利用了Microsoft最新推出的DLR,而Ruby.net則是完全利用原有的CLR實現的。IronRuby入門可參閱http://msdn.microsoft.com/zh-cn/magazine/dd434651.aspx。關於IronRuby的一些基本操作,本文不會涉及,本文僅僅是IronRuby對Ruby操作的一個具體實例。其中包括對所有Ruby類的類名,方法名以及參數列表的獲取與顯示相關的樹結構。究其原因采用IronRuby來進行操作,主要是因為通過Ruby的反射可以獲取到Ruby方法名列表,但是獲取不到方法的參數列表與參數名稱。此文僅供參考,因為本人也對IronRuby接觸不是很久,基本上是摸索出來的,難免會有錯誤的地方。
二、類圖設計
相關類圖設計如下,其中RubyScriptEngine主要負責通過IronRuby來獲取和構造相關的類名、方法名與參數列表以及之間的相關關系。TreeDrawer主要負責設計類名、方法名與參數列表相對應的樹形結構圖。

三、詳細設計
(1)RubyScriptEngine主要負責通過IronRuby來獲取和構造相關的類名、方法名與參數列表以及之間的相關關系。RubyScriptEngine將Ruby文件進行加載,然後動態獲取文件中包含的類、方法與方法參數列表。
具體代碼如下:
public static class RubyScriptEngine
{
private static readonly ScriptEngine engine = null;
static RubyScriptEngine()
{
engine = Ruby.CreateEngine();
}
public static bool InitRelativeFiles(string directory)
{
if (!Directory.Exists(directory))
{
return false;
}
string[] files = Directory.GetFiles(directory);
for (int index = 0; index < files.Length; index++)
{
InitRelativeFile(files[index]);
}
return true;
}
public static bool InitRelativeFile(string fileName)
{
if (!File.Exists(fileName))
{
return false;
}
try
{
FileInfo fileInfo = new FileInfo(fileName);
if (string.Equals(fileInfo.Extension, ".rb", StringComparison.CurrentCultureIgnoreCase))
{
engine.ExecuteFile(fileName);
}
}
catch
{
return false;
}
return true;
}
public static IList<string> GetClassNames()
{
return engine.Runtime.Globals.GetVariableNames().ToList();
}
public static IList<ClassItem> GetClassesInfos()
{
IList<string> names = GetClassNames();
IList<ClassItem> items = new List<ClassItem>();
foreach (string name in names)
{
items.Add(GetClassInfo(name));
}
return items;
}
public static ClassItem GetClassInfo(string className, params object[] parameters)
{
RubyClass rubyClass = engine.Runtime.Globals.GetVariable(className);
dynamic instance = engine.Operations.CreateInstance(rubyClass, parameters);
ClassItem classItem = new ClassItem(className);
IList<string> memberNames = engine.Operations.GetMemberNames(instance);
MethodItem methodItem = null;
ParameterItem parameterItem = null;
foreach (string memberName in memberNames)
{
RubyMethodInfo methodInfo = rubyClass.GetMethod(memberName) as RubyMethodInfo;
if (methodInfo == null)
{
continue;
}
methodItem = new MethodItem(memberName,className);
RubyArray parameterArray = methodInfo.GetRubyParameterArray();
SimpleAssignmentExpression[] expressions = methodInfo.Parameters.Optional;
for (int index = 0; index < parameterArray.Count; index++)
{
RubyArray vas = parameterArray[index] as RubyArray;
string type = vas[0].ToString();
string name = vas[1].ToString();
parameterItem = new ParameterItem(name);
if (type == "rest")
{
parameterItem.DefaultName = "*" + name;
parameterItem.Description = RubyResource.ArrayParamDesc;
}
else if (type == "opt")
{
for (int eindex = 0; eindex < expressions.Length; eindex++)
{
SimpleAssignmentExpression ex = expressions[eindex];
Variable variable = ex.Left as Variable;
if (!string.Equals(variable.Name, name))
{
continue;
}
Literal literal = ex.Right as Literal;
parameterItem.DefaultName = name;
parameterItem.DefaultValue = literal.Value;
parameterItem.Description = RubyResource.DefaultParamDesc;
}
}
else if (type == "block")
{
parameterItem.DefaultName = "&" + name;
parameterItem.Description = RubyResource.BlockParamDesc;
}
else
{
parameterItem.DefaultName = name;
}
methodItem.Parameters.Add(parameterItem);
}
classItem.Methods.Add(methodItem);
}
return classItem;
}
}
其中相關方法如下:
publicstaticbool InitRelativeFiles(string directory) 根據目錄加載目錄下的Ruby文件
publicstaticbool InitRelativeFile(string fileName) 根據文件名加載該Ruby文件
publicstatic IList<string> GetClassNames() 獲取所有的類名
publicstatic IList<ClassItem> GetClassesInfos() 獲取所有類的信息
publicstatic ClassItem GetClassInfo(string className, paramsobject[] parameters) 根據類名和類的構造函數參數獲取對應的類信息
類的信息顯示效果如下(左側顯示類的信息,右側編輯器顯示類的基本結構):

(2)TreeDrawer主要用於繪制類的樹結構,根據不同的類結構顯示不同的效果。這裡是用Winform來顯示的,本來打算用Silverlight來實現,但是由於時間關系,將就著這樣算了。當然,Silverlight顯示的效果比Winform強多了,而且,本人Silverlight水平比Winform熟練很多(以前項目中用Silverlight動態繪制相關圖形,因此比較熟悉)....
TreeDrawer的主要方法為以下2個:
public Bitmap CreateImage(ClassItem classItem, Font font)
{
if (classItem == null || font == null)
{
return null;
}
ClassBlock classBlock = CreateCurrentClassBlock(classItem);
AddLinesAndBlockTexts(classBlock, font);
Bitmap bitmap = new Bitmap(3 * BLOCK_WIDTH + 2 * BLOCK_INNER_WIDTH, this.Height);
Graphics graphics = Graphics.FromImage(bitmap);
graphics.Clear(Color.White);
Pen ellipsePen = new Pen(Color.Blue, 2);
foreach (Line line in this.Lines)
{
graphics.DrawLine(ellipsePen, line.X1, line.Y1, line.X2, line.Y2);
if (line.HasArrow)
{
PointF[] points = CreateArrowPoints(new PointF(line.X1, line.Y1),
new PointF(line.X2, line.Y2), ARROW_LENGTH, RELATIVE_VALUE);
DrawArrowHead(graphics, points);
}
}
foreach (BlockText content in this.Contents)
{
graphics.DrawString(content.Content, font, Brushes.Black, content.StartX, content.StartY);
}
return bitmap;
}
private ClassBlock CreateCurrentClassBlock(ClassItem classItem)
{
int originalParamY = 0;
int lastParamY = 0;
int currentParamY = 0;
int currentMethodY = 0;
int startMethodX = BLOCK_WIDTH + BLOCK_INNER_WIDTH;
int startParamX = 2 * BLOCK_WIDTH + 2 * BLOCK_INNER_WIDTH;
List<MethodBlock> methodBlocks = new List<MethodBlock>();
foreach (MethodItem methodItem in classItem.Methods)
{
int paramsCount = methodItem.Parameters.Count;
if (paramsCount > 0)
{
lastParamY += paramsCount * (BLOCK_HEIGHT + BLOCK_INNER_HEGIHT);
}
else
{
lastParamY += (BLOCK_HEIGHT + BLOCK_INNER_HEGIHT);
currentParamY += (BLOCK_HEIGHT + BLOCK_INNER_HEGIHT);
}
currentMethodY = ((lastParamY - BLOCK_INNER_HEGIHT - originalParamY) / 2 + originalParamY - (BLOCK_HEIGHT / 2));
originalParamY = lastParamY;
MethodBlock methodBlock = new MethodBlock(new Point(startMethodX, currentMethodY), BLOCK_WIDTH, BLOCK_HEIGHT);
methodBlock.Content = methodItem.Name;
methodBlocks.Add(methodBlock);
foreach (ParameterItem parameterItem in methodItem.Parameters)
{
ParameterBlock parameterBlock = new ParameterBlock(new Point(startParamX, currentParamY), BLOCK_WIDTH, BLOCK_HEIGHT);
parameterBlock.Content = parameterItem.DefaultName;
methodBlock.ParameterBlocks.Add(parameterBlock);
currentParamY += (BLOCK_HEIGHT + BLOCK_INNER_HEGIHT);
}
}
if (lastParamY > 0)
{
lastParamY -= BLOCK_INNER_HEGIHT;
}
else
{
lastParamY = BLOCK_HEIGHT;
}
this.Height = lastParamY;
Point classStartPoint = new Point(0, 0);
if (classItem.Methods.Count > 1)
{
int y = (methodBlocks.Last().LeftBottomPoint.Y - methodBlocks.First().LeftBottomPoint.Y) / 2;
classStartPoint = new Point(0, y);
}
ClassBlock classBlock = new ClassBlock(classStartPoint, BLOCK_WIDTH, BLOCK_HEIGHT);
classBlock.Content = classItem.Name;
foreach (MethodBlock methodBlock in methodBlocks)
{
classBlock.MethodBlocks.Add(methodBlock);
}
return classBlock;
}
public Bitmap CreateImage(ClassItem classItem, Font font) 主要負責繪制Bitmap圖片
private ClassBlock CreateCurrentClassBlock(ClassItem classItem) 主要負責將ClassItem(類信息形式)轉換成ClassBlock(坐標形式),並負責計算相應的坐標
類的樹結構顯示效果如下:

當然,還可以定義其他格式的類,顯示的效果根據類的不同繪制相應的樹結構。其中,TreeDrawer中比較簡單的算法會自動設置合理的坐標,以生成相應的樹結構坐標。本處一切以簡單進行處理,不然的話,參數設置是比較多的。
四、總結
IronRuby是.NET下的一個Ruby實現,對於實現單個類的操作來說,用.NET 4.0中的Dynamic更加方便與美觀,如調用PersonClass類的nonArgsMethod方法即可寫成如下格式:
dynamic globals= engine.Runtime.Globals; dynamic apple = globals.PersonClass.@new(); //構造實例 apple.nonArgsMethod(); //調用方法
本文中采用如下代碼進行調用,主要是為了通用性處理。通過類名稱以及構造函數的參數來動態獲取類的信息。
RubyClass rubyClass = engine.Runtime.Globals.GetVariable(className); dynamic instance = engine.Operations.CreateInstance(rubyClass, parameters); ClassItem classItem = new ClassItem(className); IList<string> memberNames = engine.Operations.GetMemberNames(instance);
源代碼下載地址:IronRuby Ruby類樹結構源碼
http://files.cnblogs.com/jasenkin/IronRuby/Jasen.IronRubyApp.rar
作者:JasenKin
出處:http://www.cnblogs.com/jasenkin/