analyst 0.13.1 → 0.14.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/analyst.gemspec +4 -3
  3. data/lib/analyst/{entity_parser/association.rb → association.rb} +0 -0
  4. data/lib/analyst/entities/begin.rb +13 -0
  5. data/lib/analyst/entities/class.rb +97 -0
  6. data/lib/analyst/entities/empty.rb +10 -0
  7. data/lib/analyst/entities/entity.rb +67 -0
  8. data/lib/analyst/entities/method.rb +41 -0
  9. data/lib/analyst/entities/method_call.rb +28 -0
  10. data/lib/analyst/entities/module.rb +33 -0
  11. data/lib/analyst/entities/root.rb +22 -0
  12. data/lib/analyst/entities/singleton_class.rb +21 -0
  13. data/lib/analyst/parser.rb +30 -55
  14. data/lib/analyst/version.rb +1 -1
  15. data/lib/analyst.rb +40 -9
  16. data/spec/class_spec.rb +30 -0
  17. data/spec/fixtures/music.rb +70 -0
  18. data/spec/method_spec.rb +9 -0
  19. data/spec/parser_spec.rb +29 -0
  20. data/spec/spec_helper.rb +1 -0
  21. metadata +38 -87
  22. data/lib/analyst/cli.rb +0 -42
  23. data/lib/analyst/entity_parser/entities/class.rb +0 -92
  24. data/lib/analyst/entity_parser/entities/empty.rb +0 -13
  25. data/lib/analyst/entity_parser/entities/entity.rb +0 -29
  26. data/lib/analyst/entity_parser/entities/method.rb +0 -16
  27. data/lib/analyst/entity_parser/entities/module.rb +0 -31
  28. data/lib/analyst/formatters/base.rb +0 -33
  29. data/lib/analyst/formatters/csv.rb +0 -43
  30. data/lib/analyst/formatters/html.rb +0 -87
  31. data/lib/analyst/formatters/html_index.rb +0 -47
  32. data/lib/analyst/formatters/templates/index.html.haml +0 -92
  33. data/lib/analyst/formatters/templates/output.html.haml +0 -114
  34. data/lib/analyst/formatters/text.rb +0 -56
  35. data/lib/analyst/fukuzatsu/analyzer.rb +0 -162
  36. data/lib/analyst/fukuzatsu/cli.rb +0 -42
  37. data/lib/analyst/fukuzatsu/entity_parser/association.rb +0 -24
  38. data/lib/analyst/fukuzatsu/entity_parser/entities/class.rb +0 -92
  39. data/lib/analyst/fukuzatsu/entity_parser/entities/empty.rb +0 -13
  40. data/lib/analyst/fukuzatsu/entity_parser/entities/entity.rb +0 -29
  41. data/lib/analyst/fukuzatsu/entity_parser/entities/method.rb +0 -16
  42. data/lib/analyst/fukuzatsu/entity_parser/entities/module.rb +0 -31
  43. data/lib/analyst/fukuzatsu/formatters/base.rb +0 -33
  44. data/lib/analyst/fukuzatsu/formatters/csv.rb +0 -43
  45. data/lib/analyst/fukuzatsu/formatters/html.rb +0 -87
  46. data/lib/analyst/fukuzatsu/formatters/html_index.rb +0 -47
  47. data/lib/analyst/fukuzatsu/formatters/templates/index.html.haml +0 -92
  48. data/lib/analyst/fukuzatsu/formatters/templates/output.html.haml +0 -114
  49. data/lib/analyst/fukuzatsu/formatters/text.rb +0 -56
  50. data/lib/analyst/fukuzatsu/line_of_code.rb +0 -19
  51. data/lib/analyst/fukuzatsu/parsed_file.rb +0 -85
  52. data/lib/analyst/fukuzatsu/parsed_method.rb +0 -32
  53. data/lib/analyst/fukuzatsu/parser_original.rb +0 -76
  54. data/lib/analyst/fukuzatsu/rethink/parser.rb +0 -346
  55. data/lib/analyst/fukuzatsu/version.rb +0 -3
  56. data/lib/analyst/line_of_code.rb +0 -19
  57. data/lib/analyst/parsed_file.rb +0 -85
  58. data/lib/analyst/parsed_method.rb +0 -32
  59. data/lib/analyst/rethink/parser.rb +0 -346
  60. data/spec/analyzer_spec.rb +0 -122
  61. data/spec/cli_spec.rb +0 -48
  62. data/spec/fixtures/eg_class.rb +0 -8
  63. data/spec/fixtures/eg_mod_class.rb +0 -2
  64. data/spec/fixtures/eg_mod_class_2.rb +0 -5
  65. data/spec/fixtures/eg_module.rb +0 -2
  66. data/spec/fixtures/module_with_class.rb +0 -9
  67. data/spec/fixtures/multiple_methods.rb +0 -7
  68. data/spec/fixtures/nested_methods.rb +0 -8
  69. data/spec/fixtures/program_1.rb +0 -19
  70. data/spec/fixtures/program_2.rb +0 -25
  71. data/spec/fixtures/program_3.rb +0 -66
  72. data/spec/fixtures/program_4.rb +0 -1
  73. data/spec/fixtures/single_class.rb +0 -9
  74. data/spec/fixtures/single_method.rb +0 -3
  75. data/spec/formatters/csv_spec.rb +0 -37
  76. data/spec/formatters/html_index_spec.rb +0 -36
  77. data/spec/formatters/html_spec.rb +0 -48
  78. data/spec/formatters/text_spec.rb +0 -39
  79. data/spec/parsed_file_spec.rb +0 -67
  80. data/spec/parsed_method_spec.rb +0 -34
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f928fc2a5aaf979b7d338ba06a0a588cd07cf98e
4
- data.tar.gz: 3108a32bef9afb6a87a26023da2b7e5e751ab774
3
+ metadata.gz: b3e243d014bb8326cde40ec9a77530dc26cd2d77
4
+ data.tar.gz: e70309a0752dacbccddd636e99e4406691a61d44
5
5
  SHA512:
6
- metadata.gz: c9a8c882c5e080ae9612f7531cbac8037c703b3ca0a73206c07db9de3e94c16021c41a7f62add65a854cefda1d2c8d8746bb72ea08125a3e84875864cb66f75c
7
- data.tar.gz: d7d3a8615db9efe5c88471b7f30dc6eedb5bf3e5caff532f0ff689abc2621a67ac9e02c12309b03d3f941ba4280ca41ff2da7f6faed5c0164f16ec7673624d7e
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{Engine for analyzing Ruby source code.}
12
- spec.description = %q{Engine for analyzing Ruby source code.}
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
@@ -0,0 +1,13 @@
1
+ module Analyst
2
+
3
+ module Entities
4
+ module Begin
5
+
6
+ def self.new(node, parent)
7
+ node.children.map { |child| Analyst::Parser.process_node(child, parent) }
8
+ end
9
+
10
+ end
11
+ end
12
+ end
13
+
@@ -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,10 @@
1
+ module Analyst
2
+
3
+ module Entities
4
+ class Empty < Entity
5
+ def full_name
6
+ ""
7
+ end
8
+ end
9
+ end
10
+ 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
@@ -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
- attr_reader :start_path, :parsed_files
8
- attr_reader :threshold, :formatter
9
- attr_reader :start_time
8
+ extend Forwardable
10
9
 
11
- OUTPUT_DIRECTORY = "doc/Analyst"
10
+ attr_reader :start_path
12
11
 
13
- def initialize(path, formatter, threshold=0)
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
- private
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 reset_output_directory
40
- begin
41
- FileUtils.remove_dir(OUTPUT_DIRECTORY)
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 source_files
48
- if File.directory?(start_path)
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 parse_source_file(path_to_file, options={})
56
- ParsedFile.new(path_to_file: path_to_file)
39
+ def inspect
40
+ "\#<#{self.class}:#{object_id}>"
57
41
  end
58
42
 
59
- def report_complexity
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 write_report_index
68
- return unless self.formatter.writes_to_file_system?
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
@@ -1,3 +1,3 @@
1
1
  module Analyst
2
- VERSION = "0.13.1"
2
+ VERSION = "0.14.0"
3
3
  end
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/cli"
8
- require_relative "analyst/formatters/base"
9
- require_relative "analyst/formatters/csv"
10
- require_relative "analyst/formatters/html"
11
- require_relative "analyst/formatters/html_index"
12
- require_relative "analyst/formatters/text"
13
- require_relative "analyst/line_of_code"
14
- require_relative "analyst/parsed_file"
15
- require_relative "analyst/parsed_method"
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
@@ -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
+
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe Analyst::Entities::InstanceMethod do
4
+
5
+ it "has specs" do
6
+ expect(false).to be_truthy
7
+ end
8
+
9
+ end