เขียนไปงงไป code อาจจะดูขัดตาหน่อย)
สืบเนื่องจากการใช้ Trac wiki มาเป็นเครื่องมือ design
ก็เลยเกิดความต้องการ อยากให้ render class diagram ใน wiki ได้ด้วย
จากการสืบค้นใน google ก็พบว่า มี plugin ที่ชื่อ graphvizplugin อยู่ตัวหนึ่ง
ซึ่งจากการทดลองใช้ ก็ใช้ได้ดี
วิธีการใช้ ก็เพียงแต่แทรก code ลงไปอย่างนี้
{{{
#!graphviz
digraph G {
....
....
}
}}}
แต่ก็ยังมีความยุ่งยากในการเขียน dot expression ให้แสดงผลเป็น
class diagram อยู่บ้าง
(จริงๆไม่ยุ่งหรอก เขียนง่ายตรงไปตรงมาดี แต่ไม่พอใจซะงั้น)
ก็เลยอยากทำ expression ที่ layer สูงขึ้นไปอีกชั้น
เช่น เขียน
class Order {
name;
passwd;
enabled;
};
แล้วให้มันแปลงเป็น แบบนี้
digraph uml {
node [shape=record,fontsize=10,fontname="Helvetica"]
struct2 [label="{User\n|name\lpasswd\lenabled}"];
วิธีการใช้งาน ก็ประมาณนี้
จาก Requirement ที่ว่ามา ก็มาถึงขั้นตอนการ implement
การ implement plugin ตัวนี้ แบ่งออกเป็น 2 ส่วนคือ
- ส่วน parser ที่ใช้แปลง custom language ของเรา ให้เป็น dot language
- ส่วน plugin ที่ register ตัวเองเป็น macro ของ Trac
และทำหน้าที่ render ให้เป็น graph ออกมา
ส่วน parser เลือกใช้ library ที่ชื่อ PLY (Python Lex-Yacc)
โดยเขียน lexer ดังนี้
import lex
# List of token names. This is always required
tokens = (
'CLASS',
'IDENT',
'SEMI',
'LBRACE',
'RBRACE'
)
# Regular expression rules for simple tokens
t_ignore = ' \t'
t_SEMI = ';'
t_LBRACE = '{'
t_RBRACE = '}'
def t_IDENT(t):
r'[a-zA-Z_][\w_]*'
# แยกความแตกต่างระหว่าง Ident กับ Reserved word
t.type = reserved.get(t.value,'IDENT')
return t
def t_newline(t):
r'\n+'
t.lineno += len(t.value)
def t_error(t):
print "Illegal character '%s'" % t.value[0]
t.skip(1)
reserved = {
'class' : 'CLASS'
}
lex.lex()
ตัว parser ของ เรามี gramma ประมาณนี้
diagram : class_list
class_list : class_list
| class_statement
class_statement : CLASS IDENT SEMI
| CLASS IDENT LBRACE class_body_list RBRACE SEMI
class_body_list : class_body_list
| class_body
class_body : IDENT SEMI
การ implement gramma ใน PLY ตรงไปตรงมาดี
เริ่มที่ class_body แล้วกัน
def p_class_body(t):
'class_body : IDENT SEMI'
t[0] = t[1]
t[0] คือค่าที่จะ return กลับไป
ส่วน t[1] ก็คือ token ลำดับที่ 1 นั่นก็คือค่าของ IDENT นั่นเอง
ส่วน class_body_list ต้อง implement แยกเป็น 2 function
def p_class_body_list(t):
'class_body_list : class_body'
lst = [t[1]]
t[0] = lst
def p_class_body_list2(t):
'class_body_list : class_body_list class_body '
t[1].append(t[2])
t[0] = t[1]
อันแรกตรงไปตรงมา class_body return อะไรมา ก็ให้แปลงเป็น list ก่อน return กลับ
ส่วนอันที่สอง datatype ของอันแรกจะเป็น list
ดังนั้นก็นำเอาสิ่งที่ return มาจาก class_body append เข้าไปยัง list ที่ได้
ก่อน return กลับไป
สรุปว่า class_body_list จะ return ค่ากลับเป็น list เสมอ
ส่วน class_statement ก็ แยกเป็น 2 ส่วนเหมือนกัน
def p_class_statement(t):
'class_statement : CLASS IDENT SEMI'
cls = TypeClass(t[2])
t[0] = cls
def p_class_statement2(t):
'class_statement : CLASS IDENT LBRACE class_body_list RBRACE SEMI'
cls = TypeClass(t[2])
cls.members = t[4]
t[0] = cls
เนื่องจาก data แถวนี้เริ่มซับซ้อนขึ้น ก็เลยต้องสร้าง class ที่ชื่อ TypeClass
ขึ้นมารองรับข้อมูลในส่วนนี้
ชุดสุดท้าย ก็คือ diagram กับ class_list
def p_diagram(t):
'diagram_statement : class_list'
d = Diagram()
d.classes = t[1]
t[0] = d
def p_class_list(t):
'class_list : class_statement'
lst = [t[1]]
t[0] = lst
def p_class_list_2(t):
'class_list : class_list class_statement'
t[1].append(t[2])
t[0] = t[1]
ตรงนี้มีการสร้าง class ที่ชื่อ
Diagram
ขึ้นมารองรับข้อมูลพอได้ structure แล้วการแปลงเป็น dot language ก็ไม่ยากแล้ว
class Diagram และ TypeClass หน้าตาเป็นแบบนี้
class Diagram(object):
def __init__(self):
self.classes = []
def to_dot(self):
buf = StringIO()
buf.write('digraph g {\n')
buf.write("node [shape=record,fontsize=10,fontname=\"Helvetica\"]\n")
for cls in self.classes:
buf.write("%s" % cls.to_dot())
buf.write('\n}')
return buf.getvalue()
class TypeClass(object):
def __init__(self, name):
self.name = name
self.members = []
def to_dot(self):
buf = StringIO()
buf.write('%s [label="{%s\\n|' % (self.name, self.name))
for member in self.members:
buf.write('%s\\l' % member)
buf.write("}\"]\n")
return buf.getvalue()
ไว้คราวหน้าจะพูดถึงประเด็นการเขียน plugin ใน Trac ต่อ
2 comments:
ผมมองว่าประโยชน์ที่ได้อาจไม่เป็นพอใจนะครับ คนออกแบบต้องมาเขียน class diagram บน trac
ทำให้ต้องทำงานเพิ่มผมว่ามันควรดึงข้อมูลจากทำการ
งานปกติ เพื่อไม่ให้ซ้ำซ้อนกันนะครับ
ยุทธ
ผมกะจะใช้ในส่วน sketch design รอบแรกครับ
ก็คือ ทำอย่างไรให้ออกมาเร็ว
(ไม่ต้องไปเสียเวลาใช้ tool วาดรูป)
มี diagram ประกอบพอให้เข้าใจ
Post a Comment