xumlidot 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/xumlidot +39 -0
- data/lib/xumlidot.rb +10 -0
- data/lib/xumlidot/diagram.rb +18 -0
- data/lib/xumlidot/diagram/dot.rb +55 -0
- data/lib/xumlidot/diagram/dot/klass.rb +75 -0
- data/lib/xumlidot/diagram/dot/module.rb +11 -0
- data/lib/xumlidot/diagram/shared/naming.rb +19 -0
- data/lib/xumlidot/diagram/xmi.rb +190 -0
- data/lib/xumlidot/diagram/xmi/argument.rb +34 -0
- data/lib/xumlidot/diagram/xmi/attribute.rb +24 -0
- data/lib/xumlidot/diagram/xmi/constant.rb +16 -0
- data/lib/xumlidot/diagram/xmi/id.rb +39 -0
- data/lib/xumlidot/diagram/xmi/klass.rb +133 -0
- data/lib/xumlidot/diagram/xmi/method.rb +38 -0
- data/lib/xumlidot/diagram/xmi/superklass.rb +18 -0
- data/lib/xumlidot/directory_tree.rb +24 -0
- data/lib/xumlidot/options.rb +104 -0
- data/lib/xumlidot/parsers.rb +14 -0
- data/lib/xumlidot/parsers/args.rb +159 -0
- data/lib/xumlidot/parsers/call.rb +92 -0
- data/lib/xumlidot/parsers/file.rb +15 -0
- data/lib/xumlidot/parsers/generic.rb +106 -0
- data/lib/xumlidot/parsers/klass_definition.rb +76 -0
- data/lib/xumlidot/parsers/method_signature.rb +95 -0
- data/lib/xumlidot/parsers/module_definition.rb +55 -0
- data/lib/xumlidot/parsers/scope.rb +54 -0
- data/lib/xumlidot/parsers/stack.rb +96 -0
- data/lib/xumlidot/types.rb +18 -0
- data/lib/xumlidot/types/argument.rb +61 -0
- data/lib/xumlidot/types/arguments.rb +14 -0
- data/lib/xumlidot/types/attribute.rb +34 -0
- data/lib/xumlidot/types/attributes.rb +11 -0
- data/lib/xumlidot/types/constant.rb +49 -0
- data/lib/xumlidot/types/constants.rb +57 -0
- data/lib/xumlidot/types/inherited_module.rb +24 -0
- data/lib/xumlidot/types/instance_methods.rb +10 -0
- data/lib/xumlidot/types/klass.rb +60 -0
- data/lib/xumlidot/types/klass_definition.rb +61 -0
- data/lib/xumlidot/types/klass_methods.rb +10 -0
- data/lib/xumlidot/types/method.rb +10 -0
- data/lib/xumlidot/types/method_signature.rb +64 -0
- data/lib/xumlidot/types/methods.rb +10 -0
- data/lib/xumlidot/types/module.rb +11 -0
- data/lib/xumlidot/types/module_definition.rb +14 -0
- data/lib/xumlidot/types/superklass.rb +32 -0
- data/spec/spec_helper.rb +2 -0
- metadata +175 -0
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../parsers'
|
4
|
+
require_relative '../types'
|
5
|
+
|
6
|
+
module Xumlidot
|
7
|
+
module Parsers
|
8
|
+
class Call < MethodBasedSexpProcessor
|
9
|
+
attr_reader :definition
|
10
|
+
|
11
|
+
def initialize(exp, klass)
|
12
|
+
super()
|
13
|
+
|
14
|
+
@klass = klass
|
15
|
+
@modules = ::Xumlidot::Types::InheritedModule.new(nil)
|
16
|
+
|
17
|
+
process(exp)
|
18
|
+
|
19
|
+
return if klass.definition.nil?
|
20
|
+
klass.definition.inherited_modules << @modules
|
21
|
+
end
|
22
|
+
|
23
|
+
def process_call(exp)
|
24
|
+
exp.shift # remove the :call
|
25
|
+
|
26
|
+
begin
|
27
|
+
recv = process(exp.shift)
|
28
|
+
rescue => e
|
29
|
+
STDERR.puts " ** bug: unable to calculate reciever for #{exp}"
|
30
|
+
end
|
31
|
+
|
32
|
+
name = exp.shift
|
33
|
+
args = exp.shift
|
34
|
+
|
35
|
+
case name
|
36
|
+
when :private, :public, :protected
|
37
|
+
::Xumlidot::Parsers::Scope.set_visibility(name)
|
38
|
+
when :include
|
39
|
+
@modules.type = :include
|
40
|
+
process(args)
|
41
|
+
when :extend
|
42
|
+
@modules.type = :extend
|
43
|
+
process(args)
|
44
|
+
when :prepend
|
45
|
+
@modules.type = :prepend
|
46
|
+
process(args)
|
47
|
+
when :module_function
|
48
|
+
# TODO: expose as an instance method on the module
|
49
|
+
when :attr_reader
|
50
|
+
add_attributes(args, exp, read: true)
|
51
|
+
when :attr_writer
|
52
|
+
add_attributes(args, exp, write: true)
|
53
|
+
when :attr_accessor
|
54
|
+
add_attributes(args, exp, read: true, write: true)
|
55
|
+
# else
|
56
|
+
# puts "CALL RECV:#{recv unless recv.nil? || recv.empty?} NAME:#{name} ARGS:#{args unless args.nil? || args.empty?}"
|
57
|
+
end
|
58
|
+
s()
|
59
|
+
end
|
60
|
+
|
61
|
+
def add_attributes(name, exp, read: false, write: false)
|
62
|
+
@klass.attributes << ::Xumlidot::Types::Attribute.new(name.value, read, write)
|
63
|
+
exp.each do |attribute|
|
64
|
+
@klass.attributes << ::Xumlidot::Types::Attribute.new(attribute.value, read, write)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def process_const(exp)
|
69
|
+
# exp.shift # remove :const
|
70
|
+
@modules << exp.value
|
71
|
+
process_until_empty(exp)
|
72
|
+
s()
|
73
|
+
end
|
74
|
+
|
75
|
+
def process_colon2(exp)
|
76
|
+
exp.shift # remove :colon2
|
77
|
+
@modules << exp.value
|
78
|
+
process_until_empty(exp)
|
79
|
+
s()
|
80
|
+
end
|
81
|
+
|
82
|
+
def process_colon3(exp)
|
83
|
+
exp.shift # remove :colon3
|
84
|
+
@modules << '::'
|
85
|
+
@modules << exp.value
|
86
|
+
process_until_empty(exp)
|
87
|
+
s()
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../parsers'
|
4
|
+
require_relative '../types'
|
5
|
+
|
6
|
+
module Xumlidot
|
7
|
+
module Parsers
|
8
|
+
# Takes a file contents (as a string) and parses it into
|
9
|
+
# s expressions using ruby parser. The parse method uses
|
10
|
+
# the SexpProcessors methods to contert to a graph
|
11
|
+
class File < Generic
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../parsers'
|
4
|
+
require_relative '../types'
|
5
|
+
|
6
|
+
module Xumlidot
|
7
|
+
module Parsers
|
8
|
+
|
9
|
+
# We need a level of indirection between the actual parser
|
10
|
+
# and MethodBasedSexpProcessor since we will probably end up inheriting
|
11
|
+
# from SexpProcessor directly eventually
|
12
|
+
#
|
13
|
+
# The File processor was getting too busy and its obvious we want to share
|
14
|
+
# some bits of the processing
|
15
|
+
class Generic < MethodBasedSexpProcessor
|
16
|
+
attr_reader :constants
|
17
|
+
|
18
|
+
def initialize(string, constants = Stack::Constants.new )
|
19
|
+
@parsed = RubyParser.new.parse(string)
|
20
|
+
@constants = constants
|
21
|
+
super()
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse
|
25
|
+
process(@parsed)
|
26
|
+
end
|
27
|
+
|
28
|
+
# CLASSES, MODULES AND DEFINITIONS
|
29
|
+
#
|
30
|
+
# We process the superclass differently since we dont want an
|
31
|
+
# actual superclass node adding - just the methods
|
32
|
+
def process_sclass(exp)
|
33
|
+
super(exp) do
|
34
|
+
Scope.public { process_until_empty(exp) } # Process the superclass with public visibility
|
35
|
+
end
|
36
|
+
rescue Exception => e
|
37
|
+
if ENV["XUMLIDOT_DEBUG"]
|
38
|
+
STDERR.puts e.backtrace.reverse
|
39
|
+
STDERR.puts "ERROR (#process_sclass) #{e.message}"
|
40
|
+
end
|
41
|
+
exp
|
42
|
+
end
|
43
|
+
|
44
|
+
def process_module(exp)
|
45
|
+
process_class(exp,
|
46
|
+
definition_parser: ::Xumlidot::Parsers::ModuleDefinition,
|
47
|
+
type: Xumlidot::Types::Module)
|
48
|
+
end
|
49
|
+
|
50
|
+
def process_class(exp,
|
51
|
+
definition_parser: ::Xumlidot::Parsers::KlassDefinition,
|
52
|
+
type: Xumlidot::Types::Klass)
|
53
|
+
|
54
|
+
Scope.set_visibility
|
55
|
+
definition = definition_parser.new(exp.dup[0..2], @class_stack).definition
|
56
|
+
|
57
|
+
STDERR.puts definition.to_s if ::Xumlidot::Options.debug == true
|
58
|
+
super(exp) do
|
59
|
+
Scope.public do
|
60
|
+
klass_or_module = type.new(definition)
|
61
|
+
@constants.add(klass_or_module)
|
62
|
+
process_until_empty(exp)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
rescue Exception => e
|
66
|
+
if ENV["XUMLIDOT_DEBUG"]
|
67
|
+
STDERR.puts e.backtrace.reverse
|
68
|
+
STDERR.puts "ERROR (#process_class) #{e.message}"
|
69
|
+
end
|
70
|
+
exp
|
71
|
+
end
|
72
|
+
|
73
|
+
# METHODS & METHOD SIGNATURES
|
74
|
+
def process_defn(exp, superclass_method = false)
|
75
|
+
method = ::Xumlidot::Parsers::MethodSignature.new(exp, superclass_method || @sclass.last)
|
76
|
+
@constants.last_added.add_method(method)
|
77
|
+
STDERR.puts method.to_s if ::Xumlidot::Options.debug == true
|
78
|
+
#super(exp) { process_until_empty(exp) } # DISABLING since parsing the method is crashing
|
79
|
+
s()
|
80
|
+
rescue Exception => e
|
81
|
+
if ENV["XUMLIDOT_DEBUG"]
|
82
|
+
STDERR.puts e.backtrace.reverse
|
83
|
+
STDERR.puts "ERROR (#process_def#{superclass_method ? 's' : 'n'}) #{e.message}"
|
84
|
+
end
|
85
|
+
s()
|
86
|
+
end
|
87
|
+
|
88
|
+
def process_defs(exp)
|
89
|
+
process_defn(exp, true)
|
90
|
+
end
|
91
|
+
|
92
|
+
# CALLS
|
93
|
+
def process_call(exp)
|
94
|
+
::Xumlidot::Parsers::Call.new(exp, @constants.last_added)
|
95
|
+
s()
|
96
|
+
rescue Exception => e
|
97
|
+
if ENV["XUMLIDOT_DEBUG"]
|
98
|
+
STDERR.puts e.backtrace.reverse
|
99
|
+
STDERR.puts "ERROR (#process_call) #{e.message}"
|
100
|
+
end
|
101
|
+
s()
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../parsers'
|
4
|
+
require_relative '../types'
|
5
|
+
|
6
|
+
module Xumlidot
|
7
|
+
module Parsers
|
8
|
+
# Parser for the KLASS DEFINITION ONLY
|
9
|
+
class KlassDefinition < MethodBasedSexpProcessor
|
10
|
+
|
11
|
+
attr_reader :definition
|
12
|
+
|
13
|
+
def initialize(exp, namespace = nil)
|
14
|
+
super()
|
15
|
+
|
16
|
+
@definition = ::Xumlidot::Types::KlassDefinition.new
|
17
|
+
@namespace = namespace.dup
|
18
|
+
|
19
|
+
process(exp)
|
20
|
+
end
|
21
|
+
|
22
|
+
def process_class(exp)
|
23
|
+
exp.shift # remove :class
|
24
|
+
definition = exp.shift
|
25
|
+
|
26
|
+
# Processes the name of the class
|
27
|
+
if Sexp === definition
|
28
|
+
case definition.sexp_type
|
29
|
+
when :colon2 then # Reached in the event that a name is a compound
|
30
|
+
name = definition.flatten
|
31
|
+
name.delete :const
|
32
|
+
name.delete :colon2
|
33
|
+
name.each do |v|
|
34
|
+
@definition.name << ::Xumlidot::Types::Constant.new(v, @namespace)
|
35
|
+
end
|
36
|
+
when :colon3 then # Reached in the event that a name begins with ::
|
37
|
+
@definition.name << ::Xumlidot::Types::Constant.new(definition.last, '::')
|
38
|
+
else
|
39
|
+
raise "unknown type #{exp.inspect}"
|
40
|
+
end
|
41
|
+
else Symbol === definition
|
42
|
+
#if we have a symbol we have the actual class name
|
43
|
+
# e.g. class Foo; end
|
44
|
+
@definition.name << ::Xumlidot::Types::Constant.new(definition, @namespace)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Processess inheritance
|
48
|
+
process_until_empty(exp)
|
49
|
+
|
50
|
+
s()
|
51
|
+
end
|
52
|
+
|
53
|
+
def process_const(exp)
|
54
|
+
# TODO: may have removed a shift by mistake
|
55
|
+
@definition.superklass << exp.value
|
56
|
+
process_until_empty(exp)
|
57
|
+
s()
|
58
|
+
end
|
59
|
+
|
60
|
+
def process_colon2(exp)
|
61
|
+
exp.shift # remove :colon2
|
62
|
+
@definition.superklass << exp.value
|
63
|
+
process_until_empty(exp)
|
64
|
+
s()
|
65
|
+
end
|
66
|
+
|
67
|
+
def process_colon3(exp)
|
68
|
+
exp.shift # remove :colon3
|
69
|
+
@definition.superklass << '::'
|
70
|
+
@definition.superklass << exp.value
|
71
|
+
process_until_empty(exp)
|
72
|
+
s()
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../parsers'
|
4
|
+
require_relative '../types'
|
5
|
+
|
6
|
+
module Xumlidot
|
7
|
+
module Parsers
|
8
|
+
# Parser for the arguments to a method
|
9
|
+
#
|
10
|
+
# e.g. formats def method(a, b = nil)
|
11
|
+
# to a string 'a, b = nil'
|
12
|
+
#
|
13
|
+
class MethodSignature < MethodBasedSexpProcessor
|
14
|
+
|
15
|
+
# Container for values assigned to a variable
|
16
|
+
#class Assignments < Hash
|
17
|
+
#end
|
18
|
+
|
19
|
+
attr_reader :definition
|
20
|
+
|
21
|
+
def initialize(exp, superclass_method = false)
|
22
|
+
super()
|
23
|
+
|
24
|
+
@definition = ::Xumlidot::Types::MethodSignature.new
|
25
|
+
@definition.visibility = Scope.get_visibility
|
26
|
+
@definition.args = Args.new(exp.dup[0..2]).definition # only pass the method definition into args
|
27
|
+
@definition.superclass_method = superclass_method
|
28
|
+
|
29
|
+
#@assignments = Assignments.new
|
30
|
+
|
31
|
+
process(exp)
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
@definition.to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
def process_defn(exp)
|
39
|
+
exp.shift unless auto_shift_type # node type
|
40
|
+
exp.shift if exp.first.is_a?(Sexp) && exp.first.value == :self # remove :self
|
41
|
+
|
42
|
+
@definition.name = exp.shift
|
43
|
+
@definition.file = exp.file
|
44
|
+
@definition.line_number = exp.line
|
45
|
+
@definition.line_max = exp.line_max
|
46
|
+
|
47
|
+
more = exp.shift
|
48
|
+
process(more) if more.is_a?(Sexp) && !more.empty?
|
49
|
+
s()
|
50
|
+
rescue Exception => e
|
51
|
+
STDERR.puts " ** bug: unable to proces defn #{exp}"
|
52
|
+
if ENV["XUMLIDOT_DEBUG"]
|
53
|
+
STDERR.puts "ERROR (MethodSignature#process_defn) #{e.message}"
|
54
|
+
STDERR.puts e.backtrace.reverse
|
55
|
+
end
|
56
|
+
s()
|
57
|
+
end
|
58
|
+
|
59
|
+
def process_defs(exp)
|
60
|
+
process_defn(exp)
|
61
|
+
end
|
62
|
+
|
63
|
+
# CALLS
|
64
|
+
# TODO: We need a seperate assignment class to parse these
|
65
|
+
# especially assignments so that we can attempt to work out types
|
66
|
+
def process_call(exp)
|
67
|
+
exp.shift # remove the :call
|
68
|
+
|
69
|
+
recv = process(exp.shift) # recv
|
70
|
+
name = exp.shift
|
71
|
+
args = process(exp.shift) # args
|
72
|
+
|
73
|
+
exp
|
74
|
+
rescue Exception => e
|
75
|
+
if ENV["XUMLIDOT_DEBUG"]
|
76
|
+
STDERR.puts "ERROR (MethodSignature#process_call) #{e.message}"
|
77
|
+
STDERR.puts e.backtrace.reverse
|
78
|
+
end
|
79
|
+
exp
|
80
|
+
end
|
81
|
+
|
82
|
+
def process_lasgn(exp)
|
83
|
+
exp.shift # remove :lasgn
|
84
|
+
|
85
|
+
name = exp.shift.to_s
|
86
|
+
value = exp.shift
|
87
|
+
|
88
|
+
#@assignments[name] = value
|
89
|
+
|
90
|
+
process(value)
|
91
|
+
s()
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../parsers'
|
4
|
+
require_relative '../types'
|
5
|
+
|
6
|
+
module Xumlidot
|
7
|
+
module Parsers
|
8
|
+
# Parser for the KLASS DEFINITION ONLY and the name
|
9
|
+
# probably should be changed to reflect that
|
10
|
+
#
|
11
|
+
# The main parser will handle method,
|
12
|
+
# constants, etc
|
13
|
+
#
|
14
|
+
class ModuleDefinition < MethodBasedSexpProcessor
|
15
|
+
|
16
|
+
attr_reader :definition
|
17
|
+
|
18
|
+
def initialize(exp, namespace = nil)
|
19
|
+
super()
|
20
|
+
|
21
|
+
@definition = ::Xumlidot::Types::ModuleDefinition.new
|
22
|
+
@namespace = namespace.dup
|
23
|
+
|
24
|
+
process(exp)
|
25
|
+
end
|
26
|
+
|
27
|
+
def process_module(exp)
|
28
|
+
exp.shift # remove :module
|
29
|
+
definition = exp.shift
|
30
|
+
|
31
|
+
# Processes the name of the module
|
32
|
+
if Sexp === definition
|
33
|
+
case definition.sexp_type
|
34
|
+
when :colon2 then # Reached in the event that a name is a compound
|
35
|
+
name = definition.flatten
|
36
|
+
name.delete :const
|
37
|
+
name.delete :colon2
|
38
|
+
name.each do |v|
|
39
|
+
@definition.name << ::Xumlidot::Types::Constant.new(v, @namespace)
|
40
|
+
end
|
41
|
+
when :colon3 then # Reached in the event that a name begins with ::
|
42
|
+
@definition.name << ::Xumlidot::Types::Constant.new(definition.last, '::')
|
43
|
+
else
|
44
|
+
raise "unknown type #{exp.inspect}"
|
45
|
+
end
|
46
|
+
else Symbol === definition
|
47
|
+
#if we have a symbol we have the actual module name
|
48
|
+
# e.g. module Foo; end
|
49
|
+
@definition.name << ::Xumlidot::Types::Constant.new(definition, @namespace)
|
50
|
+
end
|
51
|
+
s()
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../parsers'
|
4
|
+
require_relative '../types'
|
5
|
+
|
6
|
+
module Xumlidot
|
7
|
+
module Parsers
|
8
|
+
|
9
|
+
# Save current visibility and restore it after processing
|
10
|
+
module Scope
|
11
|
+
# Maintains current state of method visability
|
12
|
+
class Visibility
|
13
|
+
class << self
|
14
|
+
|
15
|
+
def state
|
16
|
+
@state ||= :public
|
17
|
+
end
|
18
|
+
|
19
|
+
def public
|
20
|
+
@state = :public
|
21
|
+
end
|
22
|
+
|
23
|
+
def protected
|
24
|
+
@state = :protected
|
25
|
+
end
|
26
|
+
|
27
|
+
def private
|
28
|
+
@state = :private
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def public(&block)
|
34
|
+
temp_visibility = get_visibility
|
35
|
+
set_visibility
|
36
|
+
yield if block_given?
|
37
|
+
set_visibility(temp_visibility)
|
38
|
+
end
|
39
|
+
|
40
|
+
def set_visibility(state = :public)
|
41
|
+
Visibility.send(state)
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_visibility
|
45
|
+
Visibility.state
|
46
|
+
end
|
47
|
+
|
48
|
+
module_function :set_visibility
|
49
|
+
module_function :get_visibility
|
50
|
+
module_function :public
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|