xumlidot 0.1.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.
- 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
|