analyst 0.13.1 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
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