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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/bin/xumlidot +39 -0
  3. data/lib/xumlidot.rb +10 -0
  4. data/lib/xumlidot/diagram.rb +18 -0
  5. data/lib/xumlidot/diagram/dot.rb +55 -0
  6. data/lib/xumlidot/diagram/dot/klass.rb +75 -0
  7. data/lib/xumlidot/diagram/dot/module.rb +11 -0
  8. data/lib/xumlidot/diagram/shared/naming.rb +19 -0
  9. data/lib/xumlidot/diagram/xmi.rb +190 -0
  10. data/lib/xumlidot/diagram/xmi/argument.rb +34 -0
  11. data/lib/xumlidot/diagram/xmi/attribute.rb +24 -0
  12. data/lib/xumlidot/diagram/xmi/constant.rb +16 -0
  13. data/lib/xumlidot/diagram/xmi/id.rb +39 -0
  14. data/lib/xumlidot/diagram/xmi/klass.rb +133 -0
  15. data/lib/xumlidot/diagram/xmi/method.rb +38 -0
  16. data/lib/xumlidot/diagram/xmi/superklass.rb +18 -0
  17. data/lib/xumlidot/directory_tree.rb +24 -0
  18. data/lib/xumlidot/options.rb +104 -0
  19. data/lib/xumlidot/parsers.rb +14 -0
  20. data/lib/xumlidot/parsers/args.rb +159 -0
  21. data/lib/xumlidot/parsers/call.rb +92 -0
  22. data/lib/xumlidot/parsers/file.rb +15 -0
  23. data/lib/xumlidot/parsers/generic.rb +106 -0
  24. data/lib/xumlidot/parsers/klass_definition.rb +76 -0
  25. data/lib/xumlidot/parsers/method_signature.rb +95 -0
  26. data/lib/xumlidot/parsers/module_definition.rb +55 -0
  27. data/lib/xumlidot/parsers/scope.rb +54 -0
  28. data/lib/xumlidot/parsers/stack.rb +96 -0
  29. data/lib/xumlidot/types.rb +18 -0
  30. data/lib/xumlidot/types/argument.rb +61 -0
  31. data/lib/xumlidot/types/arguments.rb +14 -0
  32. data/lib/xumlidot/types/attribute.rb +34 -0
  33. data/lib/xumlidot/types/attributes.rb +11 -0
  34. data/lib/xumlidot/types/constant.rb +49 -0
  35. data/lib/xumlidot/types/constants.rb +57 -0
  36. data/lib/xumlidot/types/inherited_module.rb +24 -0
  37. data/lib/xumlidot/types/instance_methods.rb +10 -0
  38. data/lib/xumlidot/types/klass.rb +60 -0
  39. data/lib/xumlidot/types/klass_definition.rb +61 -0
  40. data/lib/xumlidot/types/klass_methods.rb +10 -0
  41. data/lib/xumlidot/types/method.rb +10 -0
  42. data/lib/xumlidot/types/method_signature.rb +64 -0
  43. data/lib/xumlidot/types/methods.rb +10 -0
  44. data/lib/xumlidot/types/module.rb +11 -0
  45. data/lib/xumlidot/types/module_definition.rb +14 -0
  46. data/lib/xumlidot/types/superklass.rb +32 -0
  47. data/spec/spec_helper.rb +2 -0
  48. 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