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 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