umlify 0.6.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|