analyst 0.13.1 → 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/analyst.gemspec +4 -3
- data/lib/analyst/{entity_parser/association.rb → association.rb} +0 -0
- data/lib/analyst/entities/begin.rb +13 -0
- data/lib/analyst/entities/class.rb +97 -0
- data/lib/analyst/entities/empty.rb +10 -0
- data/lib/analyst/entities/entity.rb +67 -0
- data/lib/analyst/entities/method.rb +41 -0
- data/lib/analyst/entities/method_call.rb +28 -0
- data/lib/analyst/entities/module.rb +33 -0
- data/lib/analyst/entities/root.rb +22 -0
- data/lib/analyst/entities/singleton_class.rb +21 -0
- data/lib/analyst/parser.rb +30 -55
- data/lib/analyst/version.rb +1 -1
- data/lib/analyst.rb +40 -9
- data/spec/class_spec.rb +30 -0
- data/spec/fixtures/music.rb +70 -0
- data/spec/method_spec.rb +9 -0
- data/spec/parser_spec.rb +29 -0
- data/spec/spec_helper.rb +1 -0
- metadata +38 -87
- data/lib/analyst/cli.rb +0 -42
- data/lib/analyst/entity_parser/entities/class.rb +0 -92
- data/lib/analyst/entity_parser/entities/empty.rb +0 -13
- data/lib/analyst/entity_parser/entities/entity.rb +0 -29
- data/lib/analyst/entity_parser/entities/method.rb +0 -16
- data/lib/analyst/entity_parser/entities/module.rb +0 -31
- data/lib/analyst/formatters/base.rb +0 -33
- data/lib/analyst/formatters/csv.rb +0 -43
- data/lib/analyst/formatters/html.rb +0 -87
- data/lib/analyst/formatters/html_index.rb +0 -47
- data/lib/analyst/formatters/templates/index.html.haml +0 -92
- data/lib/analyst/formatters/templates/output.html.haml +0 -114
- data/lib/analyst/formatters/text.rb +0 -56
- data/lib/analyst/fukuzatsu/analyzer.rb +0 -162
- data/lib/analyst/fukuzatsu/cli.rb +0 -42
- data/lib/analyst/fukuzatsu/entity_parser/association.rb +0 -24
- data/lib/analyst/fukuzatsu/entity_parser/entities/class.rb +0 -92
- data/lib/analyst/fukuzatsu/entity_parser/entities/empty.rb +0 -13
- data/lib/analyst/fukuzatsu/entity_parser/entities/entity.rb +0 -29
- data/lib/analyst/fukuzatsu/entity_parser/entities/method.rb +0 -16
- data/lib/analyst/fukuzatsu/entity_parser/entities/module.rb +0 -31
- data/lib/analyst/fukuzatsu/formatters/base.rb +0 -33
- data/lib/analyst/fukuzatsu/formatters/csv.rb +0 -43
- data/lib/analyst/fukuzatsu/formatters/html.rb +0 -87
- data/lib/analyst/fukuzatsu/formatters/html_index.rb +0 -47
- data/lib/analyst/fukuzatsu/formatters/templates/index.html.haml +0 -92
- data/lib/analyst/fukuzatsu/formatters/templates/output.html.haml +0 -114
- data/lib/analyst/fukuzatsu/formatters/text.rb +0 -56
- data/lib/analyst/fukuzatsu/line_of_code.rb +0 -19
- data/lib/analyst/fukuzatsu/parsed_file.rb +0 -85
- data/lib/analyst/fukuzatsu/parsed_method.rb +0 -32
- data/lib/analyst/fukuzatsu/parser_original.rb +0 -76
- data/lib/analyst/fukuzatsu/rethink/parser.rb +0 -346
- data/lib/analyst/fukuzatsu/version.rb +0 -3
- data/lib/analyst/line_of_code.rb +0 -19
- data/lib/analyst/parsed_file.rb +0 -85
- data/lib/analyst/parsed_method.rb +0 -32
- data/lib/analyst/rethink/parser.rb +0 -346
- data/spec/analyzer_spec.rb +0 -122
- data/spec/cli_spec.rb +0 -48
- data/spec/fixtures/eg_class.rb +0 -8
- data/spec/fixtures/eg_mod_class.rb +0 -2
- data/spec/fixtures/eg_mod_class_2.rb +0 -5
- data/spec/fixtures/eg_module.rb +0 -2
- data/spec/fixtures/module_with_class.rb +0 -9
- data/spec/fixtures/multiple_methods.rb +0 -7
- data/spec/fixtures/nested_methods.rb +0 -8
- data/spec/fixtures/program_1.rb +0 -19
- data/spec/fixtures/program_2.rb +0 -25
- data/spec/fixtures/program_3.rb +0 -66
- data/spec/fixtures/program_4.rb +0 -1
- data/spec/fixtures/single_class.rb +0 -9
- data/spec/fixtures/single_method.rb +0 -3
- data/spec/formatters/csv_spec.rb +0 -37
- data/spec/formatters/html_index_spec.rb +0 -36
- data/spec/formatters/html_spec.rb +0 -48
- data/spec/formatters/text_spec.rb +0 -39
- data/spec/parsed_file_spec.rb +0 -67
- data/spec/parsed_method_spec.rb +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b3e243d014bb8326cde40ec9a77530dc26cd2d77
|
4
|
+
data.tar.gz: e70309a0752dacbccddd636e99e4406691a61d44
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fdb9ed57212aca0e6e6cdc243519989e34535cd43342f8c5773751122fb6795b73b2d1ce6282349ae4af8777ad0214709748fda1f05a274cd2fb50263d368c5e
|
7
|
+
data.tar.gz: 9e0456a47ff03f707d1dfa30ae61bfaf17501ffd5e24786a6c4c448fb478d2ccb8326e4aae6eaef759e15b9dbba54e8c7e6d0d2dfb5f7a80d47b2f243a9f5166
|
data/analyst.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = Analyst::VERSION
|
9
9
|
spec.authors = ["Coraline Ada Ehmke", "Mike Ziwisky"]
|
10
10
|
spec.email = ["coraline@idolhands.com", "mikezx@gmail.com"]
|
11
|
-
spec.summary = %q{
|
12
|
-
spec.description = %q{
|
11
|
+
spec.summary = %q{A nice API for interacting with parsed Ruby source code.}
|
12
|
+
spec.description = %q{A nice API for interacting with parsed Ruby source code.}
|
13
13
|
spec.homepage = ""
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_dependency "ephemeral"
|
21
|
+
spec.add_dependency "ephemeral", ">= 2.4.0"
|
22
22
|
spec.add_dependency "poro_plus"
|
23
23
|
spec.add_dependency "haml"
|
24
24
|
spec.add_dependency "parser"
|
@@ -31,5 +31,6 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.add_development_dependency "rake"
|
32
32
|
spec.add_development_dependency "rspec"
|
33
33
|
spec.add_development_dependency "simplecov"
|
34
|
+
spec.add_development_dependency "pry"
|
34
35
|
|
35
36
|
end
|
File without changes
|
@@ -0,0 +1,97 @@
|
|
1
|
+
#TODO add == to association
|
2
|
+
# TODO look thru the singleton_methods for ones on (self),
|
3
|
+
# and also look for the ones from 'class << self' constructs, which will be
|
4
|
+
# found in (sclass) nodes (which will be some sort of Entity)
|
5
|
+
|
6
|
+
module Analyst
|
7
|
+
|
8
|
+
module Entities
|
9
|
+
class Class < Analyst::Entities::Module
|
10
|
+
|
11
|
+
alias :macros :method_calls
|
12
|
+
|
13
|
+
def imethods
|
14
|
+
@imethods ||= contents.select { |entity| entity.is_a? Analyst::Entities::InstanceMethod }
|
15
|
+
end
|
16
|
+
|
17
|
+
def cmethods
|
18
|
+
some_methods = smethods.select { |method| method.target.type == :self }
|
19
|
+
other_methods = singleton_class_blocks { |block| block.target.type == :self }.map(&:smethods).flatten
|
20
|
+
some_methods + other_methods
|
21
|
+
end
|
22
|
+
|
23
|
+
def singleton_class_blocks
|
24
|
+
contents.select { |entity| entity.is_a? Analyst::Entities::SingletonClass }
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def smethods
|
30
|
+
@smethods ||= contents.select do |entity|
|
31
|
+
entity.is_a? Analyst::Entities::SingletonMethod
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# ASSOCIATIONS = [:belongs_to, :has_one, :has_many, :has_and_belongs_to_many]
|
36
|
+
|
37
|
+
# attr_reader :associations
|
38
|
+
|
39
|
+
# def initialize(node, parent)
|
40
|
+
# @associations = []
|
41
|
+
# super
|
42
|
+
# end
|
43
|
+
|
44
|
+
# def handle_send(node)
|
45
|
+
# target, method_name, *args = node.children
|
46
|
+
# if ASSOCIATIONS.include?(method_name)
|
47
|
+
# add_association(method_name, args)
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
|
51
|
+
# # When a class is reopened, merge its associations
|
52
|
+
# def merge_associations_from(klass)
|
53
|
+
# klass.associations.each do |association|
|
54
|
+
# associations << Association.new(
|
55
|
+
# type: association.type,
|
56
|
+
# source: self,
|
57
|
+
# target_class: association.target_class
|
58
|
+
# )
|
59
|
+
# end
|
60
|
+
# associations.uniq!
|
61
|
+
# end
|
62
|
+
|
63
|
+
# private
|
64
|
+
|
65
|
+
# def add_association(method_name, args)
|
66
|
+
# target_class = value_from_hash_node(args.last, :class_name)
|
67
|
+
# target_class ||= begin
|
68
|
+
# symbol_node = args.first
|
69
|
+
# symbol_name = symbol_node.children.first
|
70
|
+
# symbol_name.pluralize.classify
|
71
|
+
# end
|
72
|
+
# association = Association.new(type: method_name, source: self, target_class: target_class)
|
73
|
+
# associations << association
|
74
|
+
# end
|
75
|
+
|
76
|
+
# private
|
77
|
+
|
78
|
+
# # Fetches value from hash node iff key is symbol and value is str
|
79
|
+
# # Raises an exception if value is not str
|
80
|
+
# # Returns nil if key is not found
|
81
|
+
# def value_from_hash_node(node, key)
|
82
|
+
# return unless node.type == :hash
|
83
|
+
# pair = node.children.detect do |pair_node|
|
84
|
+
# key_symbol_node = pair_node.children.first
|
85
|
+
# key == key_symbol_node.children.first
|
86
|
+
# end
|
87
|
+
# if pair
|
88
|
+
# value_node = pair.children.last
|
89
|
+
# throw "Bad type. Expected (str), got (#{value_node.type})" unless value_node.type == :str
|
90
|
+
# value_node.children.first
|
91
|
+
# end
|
92
|
+
# end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# An entity is a named node of a given type which may have additional properties
|
2
|
+
module Analyst
|
3
|
+
module Entities
|
4
|
+
class Entity
|
5
|
+
|
6
|
+
attr_reader :parent
|
7
|
+
|
8
|
+
def initialize(ast, parent)
|
9
|
+
@parent = parent
|
10
|
+
@ast = ast
|
11
|
+
end
|
12
|
+
|
13
|
+
def handle_send_node(node)
|
14
|
+
# raise "Subclass must implement handle_send_node"
|
15
|
+
# abstract method. btw, this feels wrong -- send should be an entity too. but for now, whatevs.
|
16
|
+
end
|
17
|
+
|
18
|
+
# TODO: should every Entity have these accessors? maybe they're mixins... but would that provide any benefit?
|
19
|
+
def classes
|
20
|
+
@classes ||= begin
|
21
|
+
nested_classes = top_level_classes.map(&:classes).flatten
|
22
|
+
namespaced_classes = top_level_modules.map(&:classes).flatten
|
23
|
+
top_level_classes + nested_classes + namespaced_classes
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def top_level_modules
|
28
|
+
@top_level_modules ||= contents_of_type(Entities::Module)
|
29
|
+
end
|
30
|
+
|
31
|
+
def top_level_classes
|
32
|
+
@top_level_classes ||= contents_of_type(Entities::Class)
|
33
|
+
end
|
34
|
+
|
35
|
+
def method_calls
|
36
|
+
@method_calls ||= contents_of_type(Entities::MethodCall)
|
37
|
+
end
|
38
|
+
|
39
|
+
def full_name
|
40
|
+
throw "Subclass #{self.class.name} must implement #full_name"
|
41
|
+
end
|
42
|
+
|
43
|
+
def inspect
|
44
|
+
"\#<#{self.class}:#{object_id} full_name=#{full_name}>"
|
45
|
+
rescue
|
46
|
+
"\#<#{self.class}:#{object_id}>"
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
attr_reader :ast
|
52
|
+
|
53
|
+
def contents_of_type(klass)
|
54
|
+
contents.select { |entity| entity.is_a? klass }
|
55
|
+
end
|
56
|
+
|
57
|
+
def contents
|
58
|
+
@contents ||= Array(Analyst::Parser.process_node(content_node, self))
|
59
|
+
end
|
60
|
+
|
61
|
+
def content_node
|
62
|
+
ast.children.last
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Analyst
|
2
|
+
module Entities
|
3
|
+
|
4
|
+
class InstanceMethod < Entity
|
5
|
+
def name
|
6
|
+
ast.children.first.to_s
|
7
|
+
end
|
8
|
+
def full_name
|
9
|
+
parent.full_name + '#' + name
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# TODO HERE
|
14
|
+
class ClassMethod < Entity
|
15
|
+
def name
|
16
|
+
ast.children.first.to_s
|
17
|
+
end
|
18
|
+
def full_name
|
19
|
+
parent.full_name + '::' + name
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class SingletonMethod < Entity
|
24
|
+
|
25
|
+
# NOTE: not a public API -- used by Entities::Class
|
26
|
+
def target
|
27
|
+
target, name, params, content = ast.children
|
28
|
+
target
|
29
|
+
end
|
30
|
+
|
31
|
+
def name
|
32
|
+
ast.children[1].to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
def full_name
|
36
|
+
parent.full_name + '::' + name
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Analyst
|
2
|
+
module Entities
|
3
|
+
class MethodCall < Entity
|
4
|
+
|
5
|
+
def name
|
6
|
+
name_node.to_s
|
7
|
+
end
|
8
|
+
|
9
|
+
def full_name
|
10
|
+
name
|
11
|
+
end
|
12
|
+
|
13
|
+
# TODO: figure out how to resolve this to an Entity. we never want
|
14
|
+
# to expose the AST to the outside.
|
15
|
+
def target_node
|
16
|
+
ast.children.first
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def name_node
|
22
|
+
ast.children[1]
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Analyst
|
2
|
+
module Entities
|
3
|
+
class Module < Entity
|
4
|
+
|
5
|
+
def name
|
6
|
+
const_node_array(name_node).join('::')
|
7
|
+
end
|
8
|
+
|
9
|
+
def full_name
|
10
|
+
parent.full_name.empty? ? name : parent.full_name + '::' + name
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def name_node
|
16
|
+
ast.children.first
|
17
|
+
end
|
18
|
+
|
19
|
+
# takes a (const) node and returns an array specifying the fully-qualified
|
20
|
+
# constant name that it represents. ya know, so CoolModule::SubMod::SweetClass
|
21
|
+
# would be parsed to:
|
22
|
+
# (const
|
23
|
+
# (const
|
24
|
+
# (const nil :CoolModule) :SubMod) :SweetClass)
|
25
|
+
# and passing that node here would return [:CoolModule, :SubMod, :SweetClass]
|
26
|
+
def const_node_array(node)
|
27
|
+
return [] if node.nil?
|
28
|
+
raise "expected (const) node or nil, got (#{node.type})" unless node.type == :const
|
29
|
+
const_node_array(node.children.first) << node.children[1]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Analyst
|
2
|
+
|
3
|
+
module Entities
|
4
|
+
class Root < Entity
|
5
|
+
|
6
|
+
def full_name
|
7
|
+
""
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def contents
|
13
|
+
@contents ||= begin
|
14
|
+
child_nodes = ast.children
|
15
|
+
child_nodes.map { |child| Analyst::Parser.process_node(child, self) }.flatten
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Analyst
|
2
|
+
|
3
|
+
module Entities
|
4
|
+
class SingletonClass < Entity
|
5
|
+
|
6
|
+
def full_name
|
7
|
+
parent.full_name + "!SINGLETON"
|
8
|
+
end
|
9
|
+
|
10
|
+
def name
|
11
|
+
parent.name + "!SINGLETON"
|
12
|
+
end
|
13
|
+
|
14
|
+
def smethods
|
15
|
+
@smethods ||= contents.select { |entity| entity.is_a? Analyst::Entities::InstanceMethod }
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
data/lib/analyst/parser.rb
CHANGED
@@ -1,76 +1,51 @@
|
|
1
1
|
require 'fileutils'
|
2
|
+
require 'pry'
|
2
3
|
|
3
4
|
module Analyst
|
4
5
|
|
5
6
|
class Parser
|
6
7
|
|
7
|
-
|
8
|
-
attr_reader :threshold, :formatter
|
9
|
-
attr_reader :start_time
|
8
|
+
extend Forwardable
|
10
9
|
|
11
|
-
|
10
|
+
attr_reader :start_path
|
12
11
|
|
13
|
-
|
14
|
-
@start_path = path
|
15
|
-
@formatter = formatter
|
16
|
-
@threshold = threshold
|
17
|
-
@start_time = Time.now
|
18
|
-
reset_output_directory
|
19
|
-
end
|
20
|
-
|
21
|
-
def parsed_files
|
22
|
-
@parsed_files = source_files.map do |path_to_file|
|
23
|
-
parse_source_file(path_to_file)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def report
|
28
|
-
self.parsed_files.each do |file|
|
29
|
-
print "."
|
30
|
-
formatter.new(file, OUTPUT_DIRECTORY, file.source).export
|
31
|
-
end
|
32
|
-
puts
|
33
|
-
write_report_index
|
34
|
-
report_complexity
|
35
|
-
end
|
12
|
+
def_delegators :root, :classes, :top_level_classes
|
36
13
|
|
37
|
-
|
14
|
+
# TODO: Empty -> Unhandled (or something like that)
|
15
|
+
PROCESSORS = Hash.new(Entities::Empty).merge!(
|
16
|
+
:root => Entities::Root,
|
17
|
+
:class => Entities::Class,
|
18
|
+
:def => Entities::InstanceMethod,
|
19
|
+
:defs => Entities::SingletonMethod,
|
20
|
+
:begin => Entities::Begin,
|
21
|
+
:module => Entities::Module,
|
22
|
+
:send => Entities::MethodCall,
|
23
|
+
:sclass => Entities::SingletonClass
|
24
|
+
# :def => :method_node_parser,
|
25
|
+
# :send => :send_node_parser
|
26
|
+
# TODO: make a method parser, which pushes the the context_stack so that things inside method bodies
|
27
|
+
# are treated differently than those inside class or module bodies. same with Block (right?)
|
28
|
+
)
|
38
29
|
|
39
|
-
def
|
40
|
-
|
41
|
-
|
42
|
-
rescue Errno::ENOENT
|
43
|
-
end
|
44
|
-
FileUtils.mkpath(OUTPUT_DIRECTORY)
|
30
|
+
def self.process_node(node, parent)
|
31
|
+
return if node.nil? # TODO: maybe a Entities:Nil would be appropriate? maybe?
|
32
|
+
PROCESSORS[node.type].new(node, parent)
|
45
33
|
end
|
46
34
|
|
47
|
-
def
|
48
|
-
|
49
|
-
return Dir.glob(File.join(start_path, "**", "*.rb"))
|
50
|
-
else
|
51
|
-
return [start_path]
|
52
|
-
end
|
35
|
+
def initialize(ast)
|
36
|
+
@ast = ast
|
53
37
|
end
|
54
38
|
|
55
|
-
def
|
56
|
-
|
39
|
+
def inspect
|
40
|
+
"\#<#{self.class}:#{object_id}>"
|
57
41
|
end
|
58
42
|
|
59
|
-
|
60
|
-
return if self.threshold == 0
|
61
|
-
complexities = self.parsed_files.map(&:complexity)
|
62
|
-
return if complexities.max.to_i <= self.threshold
|
63
|
-
puts "Maximum complexity of #{complexities.max} exceeds #{options['threshold']} threshold!"
|
64
|
-
exit 1
|
65
|
-
end
|
43
|
+
private
|
66
44
|
|
67
|
-
def
|
68
|
-
|
69
|
-
puts "Results written to #{OUTPUT_DIRECTORY} "
|
70
|
-
return unless self.formatter.has_index?
|
71
|
-
formatter.index_class.new(parsed_files.map(&:summary), OUTPUT_DIRECTORY).export
|
45
|
+
def root
|
46
|
+
@root ||= self.class.process_node(@ast, nil)
|
72
47
|
end
|
73
48
|
|
74
49
|
end
|
75
50
|
|
76
|
-
end
|
51
|
+
end
|
data/lib/analyst/version.rb
CHANGED
data/lib/analyst.rb
CHANGED
@@ -4,17 +4,48 @@ require 'fileutils'
|
|
4
4
|
require 'haml'
|
5
5
|
|
6
6
|
require_relative "analyst/analyzer"
|
7
|
-
require_relative "analyst/
|
8
|
-
require_relative "analyst/
|
9
|
-
require_relative "analyst/
|
10
|
-
require_relative "analyst/
|
11
|
-
require_relative "analyst/
|
12
|
-
require_relative "analyst/
|
13
|
-
require_relative "analyst/
|
14
|
-
require_relative "analyst/
|
15
|
-
require_relative "analyst/
|
7
|
+
require_relative "analyst/entities/entity"
|
8
|
+
require_relative "analyst/entities/empty"
|
9
|
+
require_relative "analyst/entities/root"
|
10
|
+
require_relative "analyst/entities/begin"
|
11
|
+
require_relative "analyst/entities/module"
|
12
|
+
require_relative "analyst/entities/class"
|
13
|
+
require_relative "analyst/entities/method"
|
14
|
+
require_relative "analyst/entities/method_call"
|
15
|
+
require_relative "analyst/entities/singleton_class"
|
16
16
|
require_relative "analyst/parser"
|
17
17
|
require_relative "analyst/version"
|
18
18
|
|
19
19
|
module Analyst
|
20
|
+
def self.new(path_to_files)
|
21
|
+
Analyst::Parser.new(FileProcessor.new(path_to_files).ast)
|
22
|
+
end
|
23
|
+
|
24
|
+
class FileProcessor
|
25
|
+
|
26
|
+
attr_reader :path_to_files
|
27
|
+
|
28
|
+
def initialize(path_to_files)
|
29
|
+
@path_to_files = path_to_files
|
30
|
+
end
|
31
|
+
|
32
|
+
def source_files
|
33
|
+
if File.directory?(path_to_files)
|
34
|
+
return Dir.glob(File.join(path_to_files, "**", "*.rb"))
|
35
|
+
else
|
36
|
+
return [path_to_files]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def ast
|
41
|
+
::Parser::AST::Node.new(
|
42
|
+
:root, source_files.map do |file|
|
43
|
+
content = File.open(file, "r").read
|
44
|
+
::Parser::CurrentRuby.parse(content)
|
45
|
+
end
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
20
51
|
end
|
data/spec/class_spec.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Analyst::Entities::Class do
|
4
|
+
|
5
|
+
let(:parser) { Analyst.new("./spec/fixtures/music.rb") }
|
6
|
+
let(:artist) { parser.classes.detect { |klass| klass.full_name == "Artist" } }
|
7
|
+
let(:singer) { parser.classes.detect { |klass| klass.full_name == "Singer" } }
|
8
|
+
|
9
|
+
describe "#method_calls" do
|
10
|
+
it "lists all method invocations within a class definition" do
|
11
|
+
macro_names = artist.macros.map(&:name)
|
12
|
+
expect(macro_names).to match_array ["attr_accessor"]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#imethods" do
|
17
|
+
it "returns a list of instance methods" do
|
18
|
+
method_names = artist.imethods.map(&:name)
|
19
|
+
expect(method_names).to match_array ["initialize", "starve"]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#cmethods" do
|
24
|
+
it "returns a list of class methods" do
|
25
|
+
class_method_names = singer.cmethods.map(&:name)
|
26
|
+
expect(class_method_names).to match_array ["superstar", "sellouts"]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
class Artist
|
2
|
+
|
3
|
+
attr_accessor :stage_name
|
4
|
+
|
5
|
+
def initialize(name, attrs)
|
6
|
+
@name = name
|
7
|
+
@attrs = attrs
|
8
|
+
@stage_name = attrs[:stage_name]
|
9
|
+
end
|
10
|
+
|
11
|
+
def starve
|
12
|
+
Song.produce(10)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Singer < Artist
|
17
|
+
|
18
|
+
SUPER_ATTRS = { singing: 10, dancing: 10, looks: 10 }
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def sellouts
|
22
|
+
where(:album_sales > 10)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.superstar
|
27
|
+
first = %w[Michael Beyonce Cee-lo Devin]
|
28
|
+
last = %w[Jackson Knowles Green Townsend]
|
29
|
+
|
30
|
+
new("#{first.sample} #{last.sample}", SUPER_ATTRS.dup)
|
31
|
+
end
|
32
|
+
|
33
|
+
def sing
|
34
|
+
"♬ Hang the DJ! ♬"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Song
|
39
|
+
|
40
|
+
def initialize(popularity)
|
41
|
+
@popularity = popularity
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module Instruments
|
46
|
+
|
47
|
+
class Stringed
|
48
|
+
|
49
|
+
def initialize(num_strings)
|
50
|
+
@num_strings = num_strings
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Guitar < Stringed
|
55
|
+
|
56
|
+
def initialize(sound)
|
57
|
+
super(6)
|
58
|
+
@sound = sound
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
module Performances
|
65
|
+
module Equipment
|
66
|
+
class Amp
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|