umlify 0.6.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +10 -5
- data/lib/umlify/diagram.rb +7 -4
- data/lib/umlify/parser.rb +1 -2
- data/lib/umlify/parser_sexp.rb +98 -0
- data/lib/umlify/runner.rb +27 -31
- data/lib/umlify/uml_class.rb +0 -2
- data/lib/umlify/version.rb +1 -1
- data/lib/umlify.rb +1 -0
- data/test/diagram_test.rb +12 -0
- data/test/parser_sexp_test.rb +157 -0
- metadata +5 -2
data/README.md
CHANGED
@@ -39,7 +39,8 @@ Here is umlify umlified:
|
|
39
39
|
Features
|
40
40
|
--------
|
41
41
|
|
42
|
-
* __new__
|
42
|
+
* __new__ Use RubyParser instead of regular expression as of v1.0.0
|
43
|
+
* __new__ supports inhertiance (v0.4.2)
|
43
44
|
* supports associations (see "How to add associations to a diagram)
|
44
45
|
* supports methods and instance variables
|
45
46
|
|
@@ -65,11 +66,15 @@ How to add associations to a diagram
|
|
65
66
|
------------------------------------
|
66
67
|
|
67
68
|
Because of the above point, there's no direct way to automatically draw associations between your
|
68
|
-
classes. However, if you want an association to be shown on your diagram simply
|
69
|
-
on top of an `attr_accessor`, such as:
|
69
|
+
classes. However, if you want an association to be shown on your diagram simply annotate your classes such as:
|
70
70
|
|
71
|
-
# type:
|
72
|
-
|
71
|
+
# type of @weapon: Rainbow
|
72
|
+
class Unicorn
|
73
|
+
|
74
|
+
def initialize weapon
|
75
|
+
@weapon = weapon
|
76
|
+
end
|
77
|
+
end
|
73
78
|
|
74
79
|
Contribute
|
75
80
|
----------
|
data/lib/umlify/diagram.rb
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
module Umlify
|
2
2
|
|
3
|
-
##
|
4
3
|
# Creates and store a yUML api string for generating diagram
|
5
|
-
#
|
4
|
+
# type of @statements: 1..* String
|
6
5
|
class Diagram
|
7
6
|
|
8
|
-
# Array containing yUML DSL statements
|
9
7
|
attr_accessor :statements
|
10
8
|
|
11
9
|
def initialize
|
@@ -32,7 +30,12 @@ module Umlify
|
|
32
30
|
|
33
31
|
unless statement.associations.empty?
|
34
32
|
statement.associations.each do |name, type|
|
35
|
-
|
33
|
+
unless name =~ /-/
|
34
|
+
cardinality = if statement.associations[name+'-n']
|
35
|
+
' '+statement.associations[name+'-n']
|
36
|
+
end
|
37
|
+
@statements << "[#{statement.name}]-#{name}#{cardinality}>[#{type}]"
|
38
|
+
end
|
36
39
|
end
|
37
40
|
end
|
38
41
|
|
data/lib/umlify/parser.rb
CHANGED
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'ruby_parser'
|
2
|
+
|
3
|
+
module Umlify
|
4
|
+
|
5
|
+
# Parses files using S-Expressions given by the RubyParser gem
|
6
|
+
#
|
7
|
+
# type of @classes: 0..* UmlClass
|
8
|
+
#
|
9
|
+
class ParserSexp
|
10
|
+
|
11
|
+
# files should be an array containing file names with the correct path
|
12
|
+
def initialize files
|
13
|
+
@files = files
|
14
|
+
@classes = []
|
15
|
+
end
|
16
|
+
|
17
|
+
# Parses the source code of the files in @files
|
18
|
+
# to build uml classes. Returns an array containing all the
|
19
|
+
# parsed classes or nil if no ruby file were found in the
|
20
|
+
# @files array.
|
21
|
+
def parse_sources!
|
22
|
+
|
23
|
+
@source_files = @files.select {|f| f.match /\.rb/}
|
24
|
+
return nil if @source_files.empty?
|
25
|
+
|
26
|
+
@source_files.each do |file|
|
27
|
+
puts "processing #{file}..."
|
28
|
+
(parse_file File.read(file)).each {|c| @classes << c}
|
29
|
+
end
|
30
|
+
|
31
|
+
@classes
|
32
|
+
end
|
33
|
+
|
34
|
+
# Parse the given string, and return the parsed classes
|
35
|
+
def parse_file file_content
|
36
|
+
classes = []
|
37
|
+
|
38
|
+
s_exp = RubyParser.new.parse(file_content)
|
39
|
+
|
40
|
+
if s_exp[0] == :class
|
41
|
+
classes << parse_class(s_exp)
|
42
|
+
else
|
43
|
+
s_exp.each_of_type :class do |a_class|
|
44
|
+
classes << parse_class(a_class)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
classes
|
49
|
+
end
|
50
|
+
|
51
|
+
# Creates a UmlClass from a class s-expression
|
52
|
+
def parse_class class_s_exp
|
53
|
+
uml_class = UmlClass.new class_s_exp[1].to_s
|
54
|
+
|
55
|
+
# Let's start by building the associations of the class
|
56
|
+
each_association_for class_s_exp do |variable, type, cardinality|
|
57
|
+
uml_class.associations[variable] = type
|
58
|
+
uml_class.associations[variable+'-n'] = cardinality if cardinality
|
59
|
+
end
|
60
|
+
|
61
|
+
# Searching for a s(:const, :Const) right after the class name, which
|
62
|
+
# means the class inherits from a parents class, :Const
|
63
|
+
if class_s_exp[2] and class_s_exp[2][1] and class_s_exp[2][0] == :const
|
64
|
+
uml_class.parent = class_s_exp[2][1].to_s
|
65
|
+
end
|
66
|
+
|
67
|
+
# Looks-up for instance methods
|
68
|
+
class_s_exp.each_of_type :defn do |instance_method|
|
69
|
+
uml_class.methods << instance_method[1].to_s
|
70
|
+
|
71
|
+
# Now looking for @variables, inside instance methods
|
72
|
+
# I'm looking at assignments such as @var = x
|
73
|
+
instance_method.each_of_type :iasgn do |assignment|
|
74
|
+
if assignment[1] and assignment[1].class == Symbol and assignment[1].to_s =~ /@/
|
75
|
+
variable = assignment[1].to_s.gsub('@', '')
|
76
|
+
uml_class.variables << variable unless uml_class.variables.include? variable
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
uml_class
|
81
|
+
end
|
82
|
+
|
83
|
+
# Yields the variable, the type and the cardinality for each associations
|
84
|
+
def each_association_for a_class
|
85
|
+
if comments = a_class.comments
|
86
|
+
comments.split(/\n/).each do |line|
|
87
|
+
line.match(/type of @([\w]*): ([0-9.\*n]* )?([\w]*)\b/) do |m|
|
88
|
+
|
89
|
+
yield m[1], m[3], (m[2].chop if m[2])
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
data/lib/umlify/runner.rb
CHANGED
@@ -1,44 +1,30 @@
|
|
1
|
-
require 'optparse'
|
2
1
|
require 'net/http'
|
3
2
|
|
4
3
|
module Umlify
|
5
4
|
|
6
|
-
|
7
|
-
#
|
5
|
+
# Class to run to execute umlify. It parses the ruby sources provided
|
6
|
+
# and generates and save a uml diagram using yUML API.
|
7
|
+
#
|
8
|
+
# type of @diagram: Diagram
|
9
|
+
# type of @args: 0..* String
|
10
|
+
# type of @parser: ParserSexp
|
8
11
|
#
|
9
12
|
class Runner
|
10
13
|
|
11
|
-
#
|
12
|
-
attr_reader :args
|
13
|
-
|
14
|
-
# type: Parser
|
15
|
-
attr_accessor :parser
|
16
|
-
|
17
|
-
# type: Diagram
|
18
|
-
attr_accessor :diagram
|
19
|
-
|
20
|
-
# Takes as input an array with the command line options
|
14
|
+
# Takes as input an array with file names
|
21
15
|
def initialize args
|
22
16
|
@args = args
|
23
17
|
end
|
24
18
|
|
25
19
|
# Runs the application
|
26
20
|
def run
|
27
|
-
@args.push "-h" if @args.empty?
|
28
|
-
|
29
|
-
if @args[0][0] == '-'
|
30
|
-
|
31
|
-
OptionParser.new do |opts|
|
32
|
-
opts.banner = "Usage: umlify [option] [source-files directory]"
|
33
|
-
opts.on("-h", "--help", "Shows this") do
|
34
|
-
puts opts
|
35
|
-
end
|
36
|
-
end.parse! @args
|
37
21
|
|
22
|
+
if @args.empty?
|
23
|
+
puts "Usage: umlify [source directory]"
|
38
24
|
else
|
39
25
|
puts "umlifying"
|
40
26
|
|
41
|
-
@parser =
|
27
|
+
@parser = ParserSexp.new @args
|
42
28
|
|
43
29
|
if classes = @parser.parse_sources!
|
44
30
|
@diagram = Diagram.new
|
@@ -49,21 +35,31 @@ module Umlify
|
|
49
35
|
|
50
36
|
puts "Downloading the image from yUML, it shouldn't be long."
|
51
37
|
|
52
|
-
image =
|
53
|
-
|
54
|
-
end
|
55
|
-
|
56
|
-
File.open('uml.png', 'wb') do |file|
|
57
|
-
file << image.body
|
58
|
-
end if image
|
38
|
+
image = download_image
|
39
|
+
save_to_file image
|
59
40
|
|
41
|
+
puts @diagram.get_uri
|
60
42
|
puts "Saved in uml.png"
|
61
43
|
else
|
62
44
|
puts "No ruby files in the directory"
|
63
45
|
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
64
49
|
|
50
|
+
# Downloads the image of the uml diagram from yUML
|
51
|
+
def download_image
|
52
|
+
Net::HTTP.start("yuml.me", 80) do |http|
|
53
|
+
http.get(URI.escape(@diagram.get_uri))
|
65
54
|
end
|
55
|
+
end
|
66
56
|
|
57
|
+
#Saves the diagram to file
|
58
|
+
def save_to_file image
|
59
|
+
File.open('uml.png', 'wb') do |file|
|
60
|
+
file << image.body
|
61
|
+
end if image
|
67
62
|
end
|
63
|
+
|
68
64
|
end
|
69
65
|
end
|
data/lib/umlify/uml_class.rb
CHANGED
data/lib/umlify/version.rb
CHANGED
data/lib/umlify.rb
CHANGED
data/test/diagram_test.rb
CHANGED
@@ -50,6 +50,18 @@ class DiagramTest < Test::Unit::TestCase
|
|
50
50
|
assert @diagram.statements.include? '[Unicorn]-chunky>[Bacon]'
|
51
51
|
end
|
52
52
|
|
53
|
+
should "process cardinality for associations" do
|
54
|
+
test_uml_class = Umlify::UmlClass.new 'Unicorn'
|
55
|
+
test_uml_class.associations['foo'] = 'Bar'
|
56
|
+
test_uml_class.associations['foo-n'] = '1..*'
|
57
|
+
|
58
|
+
@diagram.create do
|
59
|
+
add test_uml_class
|
60
|
+
end
|
61
|
+
|
62
|
+
assert @diagram.statements.include? '[Unicorn]-foo 1..*>[Bar]'
|
63
|
+
end
|
64
|
+
|
53
65
|
should "add UmlClass with parent to diagrams" do
|
54
66
|
test_uml_class = Umlify::UmlClass.new 'Unicorn'
|
55
67
|
test_uml_class.variables << 'foo_variable'
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'shoulda'
|
2
|
+
require 'umlify'
|
3
|
+
|
4
|
+
class ParserSexpTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
context "ParserSexp" do
|
7
|
+
|
8
|
+
setup do
|
9
|
+
fixture = ["somefile.rb", "someotherfile.rb"]
|
10
|
+
@p = Umlify::ParserSexp.new fixture
|
11
|
+
end
|
12
|
+
|
13
|
+
should "respond to parse_sources!" do
|
14
|
+
assert_respond_to @p, :parse_sources!
|
15
|
+
end
|
16
|
+
|
17
|
+
should "return nil if no source file in the given directory" do
|
18
|
+
parser = Umlify::ParserSexp.new ["not_source", "still_not_source"]
|
19
|
+
assert_equal nil, parser.parse_sources!
|
20
|
+
end
|
21
|
+
|
22
|
+
should "parse class names" do
|
23
|
+
test = <<-END_FILE
|
24
|
+
class AClassName
|
25
|
+
end
|
26
|
+
END_FILE
|
27
|
+
assert_equal 'AClassName', @p.parse_file(test)[0].name
|
28
|
+
end
|
29
|
+
|
30
|
+
should "parse class name of inherited classes" do
|
31
|
+
test = <<-END_FILE
|
32
|
+
class AClassName < SuperClass
|
33
|
+
end
|
34
|
+
END_FILE
|
35
|
+
assert_equal 'AClassName', @p.parse_file(test)[0].name
|
36
|
+
assert_equal 'SuperClass', @p.parse_file(test)[0].parent
|
37
|
+
end
|
38
|
+
|
39
|
+
should "parse instance methods" do
|
40
|
+
test = <<-END_FILE
|
41
|
+
class Bar
|
42
|
+
def initialize
|
43
|
+
end
|
44
|
+
|
45
|
+
def foo
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.selfish
|
49
|
+
end
|
50
|
+
end
|
51
|
+
END_FILE
|
52
|
+
assert_equal 'Bar', @p.parse_file(test)[0].name
|
53
|
+
assert_equal ['initialize', 'foo'], @p.parse_file(test)[0].methods
|
54
|
+
end
|
55
|
+
|
56
|
+
should "parse instance variables" do
|
57
|
+
test = <<-END_FILE
|
58
|
+
class Bar
|
59
|
+
def foo
|
60
|
+
@a_variable = "foo"
|
61
|
+
@another_variable = "foo"
|
62
|
+
@@class_variable = "foo"
|
63
|
+
end
|
64
|
+
|
65
|
+
@a_class_instance_variable = "foo"
|
66
|
+
end
|
67
|
+
END_FILE
|
68
|
+
bar = @p.parse_file(test)[0]
|
69
|
+
assert_instance_of Umlify::UmlClass, bar
|
70
|
+
assert bar.variables.include? "a_variable"
|
71
|
+
assert bar.variables.include? "another_variable"
|
72
|
+
assert_equal false, bar.variables.include?('a_class_instance_variable')
|
73
|
+
assert_equal false, bar.variables.include?('class_variable')
|
74
|
+
end
|
75
|
+
|
76
|
+
should "Create associations when the types are specified" do
|
77
|
+
test = <<-END_FILE
|
78
|
+
# Describe the class's instance variables like that:
|
79
|
+
#
|
80
|
+
# type of @unicorn: Unicorn
|
81
|
+
# type of @quackable: Duck
|
82
|
+
# type of @edible: 1..* Cow
|
83
|
+
class Bar
|
84
|
+
|
85
|
+
def foo
|
86
|
+
@unicorn = Unicorn.new
|
87
|
+
@quackable = Duck.new
|
88
|
+
@edible = Cow.new
|
89
|
+
end
|
90
|
+
end
|
91
|
+
END_FILE
|
92
|
+
bar = @p.parse_file(test)[0]
|
93
|
+
assert_instance_of Umlify::UmlClass, bar
|
94
|
+
assert_equal "Unicorn", bar.associations['unicorn']
|
95
|
+
assert_equal "Duck", bar.associations['quackable']
|
96
|
+
assert_equal "Cow", bar.associations['edible']
|
97
|
+
assert_equal "1..*", bar.associations['edible-n']
|
98
|
+
end
|
99
|
+
|
100
|
+
should "parse inherited classes" do
|
101
|
+
test = <<-END_FILE
|
102
|
+
class Bar < Hash
|
103
|
+
|
104
|
+
def initialize
|
105
|
+
end
|
106
|
+
|
107
|
+
def save
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
END_FILE
|
112
|
+
bar = @p.parse_file(test)[0]
|
113
|
+
assert_instance_of Umlify::UmlClass, bar
|
114
|
+
assert_equal "Hash", bar.parent
|
115
|
+
end
|
116
|
+
|
117
|
+
should "parse inheritance from other modules" do
|
118
|
+
test = <<-END_FILE
|
119
|
+
class Bar < SomeModule::Hash
|
120
|
+
|
121
|
+
def initialize
|
122
|
+
end
|
123
|
+
|
124
|
+
def save
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
END_FILE
|
129
|
+
bar = @p.parse_file(test)[0]
|
130
|
+
assert_instance_of Umlify::UmlClass, bar
|
131
|
+
assert_equal "Hash", bar.parent
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
should "parse file with multiple classes" do
|
136
|
+
test = <<-END_FILE
|
137
|
+
class Bar < Hash
|
138
|
+
end
|
139
|
+
|
140
|
+
class Foo
|
141
|
+
end
|
142
|
+
END_FILE
|
143
|
+
|
144
|
+
classes = @p.parse_file(test)
|
145
|
+
assert_equal 2, classes.count
|
146
|
+
end
|
147
|
+
|
148
|
+
should "return an array of UmlClasses when the parsing is done" do
|
149
|
+
p = Umlify::ParserSexp.new Dir[File.dirname(__FILE__)+'/fixtures/*']
|
150
|
+
parsed_classes = p.parse_sources!
|
151
|
+
puts "here : #{parsed_classes}"
|
152
|
+
assert_equal 3, parsed_classes.count
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: umlify
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 1.0.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Michael Sokol
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-03-
|
13
|
+
date: 2011-03-06 00:00:00 -05:00
|
14
14
|
default_executable:
|
15
15
|
dependencies: []
|
16
16
|
|
@@ -30,11 +30,13 @@ files:
|
|
30
30
|
- lib/umlify/version.rb
|
31
31
|
- lib/umlify/runner.rb
|
32
32
|
- lib/umlify/parser.rb
|
33
|
+
- lib/umlify/parser_sexp.rb
|
33
34
|
- lib/umlify/extension.rb
|
34
35
|
- lib/umlify/uml_class.rb
|
35
36
|
- lib/umlify/diagram.rb
|
36
37
|
- test/parser_test.rb
|
37
38
|
- test/diagram_test.rb
|
39
|
+
- test/parser_sexp_test.rb
|
38
40
|
has_rdoc: true
|
39
41
|
homepage: https://github.com/mikaa123/umlify
|
40
42
|
licenses: []
|
@@ -65,4 +67,5 @@ specification_version: 3
|
|
65
67
|
summary: umlify is a tool that creates class diagrams from your code.
|
66
68
|
test_files:
|
67
69
|
- test/parser_test.rb
|
70
|
+
- test/parser_sexp_test.rb
|
68
71
|
- test/diagram_test.rb
|