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 CHANGED
@@ -39,7 +39,8 @@ Here is umlify umlified:
39
39
  Features
40
40
  --------
41
41
 
42
- * __new__ now supports inhertiance (v0.4.2)
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 add an annotation
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: Unicorn
72
- attr_accessor :animal
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
  ----------
@@ -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
- @statements << "[#{statement.name}]-#{name}>[#{type}]"
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
@@ -1,9 +1,8 @@
1
1
  module Umlify
2
2
 
3
- ##
3
+ # First version of the ruby parser, using regular expression.
4
4
  # Parser is responsible for parsing ruby source files and building an array
5
5
  # of uml classes
6
- #
7
6
  class Parser
8
7
 
9
8
  # An array containing all the parsed classes
@@ -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
- # This class is instanciated and run by the bin file
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
- # Contains the options from the command line
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 = Parser.new @args
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 = Net::HTTP.start("yuml.me", 80) do |http|
53
- http.get(URI.escape(@diagram.get_uri))
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
@@ -1,8 +1,6 @@
1
1
  module Umlify
2
2
 
3
- ##
4
3
  # Represent a parsed uml class
5
- #
6
4
  class UmlClass
7
5
  attr_accessor :name, :variables, :methods, :associations, :parent
8
6
 
@@ -1,3 +1,3 @@
1
1
  module Umlify
2
- VERSION = "0.6.1"
2
+ VERSION = "1.0.0"
3
3
  end
data/lib/umlify.rb CHANGED
@@ -33,6 +33,7 @@
33
33
  require 'umlify/version'
34
34
  require 'umlify/runner'
35
35
  require 'umlify/parser'
36
+ require 'umlify/parser_sexp'
36
37
  require 'umlify/extension'
37
38
  require 'umlify/uml_class'
38
39
  require 'umlify/diagram'
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.6.1
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-04 00:00:00 -05:00
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