analyst 0.0.1 → 0.13.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -1
  3. data/.rspec +2 -0
  4. data/CODE_OF_CONDUCT.md +13 -0
  5. data/README.md +1 -0
  6. data/analyst.gemspec +12 -0
  7. data/lib/analyst/analyzer.rb +162 -0
  8. data/lib/analyst/cli.rb +42 -0
  9. data/lib/analyst/entity_parser/association.rb +24 -0
  10. data/lib/analyst/entity_parser/entities/class.rb +92 -0
  11. data/lib/analyst/entity_parser/entities/empty.rb +13 -0
  12. data/lib/analyst/entity_parser/entities/entity.rb +29 -0
  13. data/lib/analyst/entity_parser/entities/method.rb +16 -0
  14. data/lib/analyst/entity_parser/entities/module.rb +31 -0
  15. data/lib/analyst/formatters/base.rb +33 -0
  16. data/lib/analyst/formatters/csv.rb +43 -0
  17. data/lib/analyst/formatters/html.rb +87 -0
  18. data/lib/analyst/formatters/html_index.rb +47 -0
  19. data/lib/analyst/formatters/templates/index.html.haml +92 -0
  20. data/lib/analyst/formatters/templates/output.html.haml +114 -0
  21. data/lib/analyst/formatters/text.rb +56 -0
  22. data/lib/analyst/fukuzatsu/analyzer.rb +162 -0
  23. data/lib/analyst/fukuzatsu/cli.rb +42 -0
  24. data/lib/analyst/fukuzatsu/entity_parser/association.rb +24 -0
  25. data/lib/analyst/fukuzatsu/entity_parser/entities/class.rb +92 -0
  26. data/lib/analyst/fukuzatsu/entity_parser/entities/empty.rb +13 -0
  27. data/lib/analyst/fukuzatsu/entity_parser/entities/entity.rb +29 -0
  28. data/lib/analyst/fukuzatsu/entity_parser/entities/method.rb +16 -0
  29. data/lib/analyst/fukuzatsu/entity_parser/entities/module.rb +31 -0
  30. data/lib/analyst/fukuzatsu/formatters/base.rb +33 -0
  31. data/lib/analyst/fukuzatsu/formatters/csv.rb +43 -0
  32. data/lib/analyst/fukuzatsu/formatters/html.rb +87 -0
  33. data/lib/analyst/fukuzatsu/formatters/html_index.rb +47 -0
  34. data/lib/analyst/fukuzatsu/formatters/templates/index.html.haml +92 -0
  35. data/lib/analyst/fukuzatsu/formatters/templates/output.html.haml +114 -0
  36. data/lib/analyst/fukuzatsu/formatters/text.rb +56 -0
  37. data/lib/analyst/fukuzatsu/line_of_code.rb +19 -0
  38. data/lib/analyst/fukuzatsu/parsed_file.rb +85 -0
  39. data/lib/analyst/fukuzatsu/parsed_method.rb +32 -0
  40. data/lib/analyst/fukuzatsu/parser_original.rb +76 -0
  41. data/lib/analyst/fukuzatsu/rethink/parser.rb +346 -0
  42. data/lib/analyst/fukuzatsu/version.rb +3 -0
  43. data/lib/analyst/line_of_code.rb +19 -0
  44. data/lib/analyst/parsed_file.rb +85 -0
  45. data/lib/analyst/parsed_method.rb +32 -0
  46. data/lib/analyst/parser.rb +76 -0
  47. data/lib/analyst/rethink/parser.rb +346 -0
  48. data/lib/analyst/version.rb +1 -1
  49. data/lib/analyst.rb +17 -2
  50. data/spec/analyzer_spec.rb +122 -0
  51. data/spec/cli_spec.rb +48 -0
  52. data/spec/fixtures/eg_class.rb +8 -0
  53. data/spec/fixtures/eg_mod_class.rb +2 -0
  54. data/spec/fixtures/eg_mod_class_2.rb +5 -0
  55. data/spec/fixtures/eg_module.rb +2 -0
  56. data/spec/fixtures/module_with_class.rb +9 -0
  57. data/spec/fixtures/multiple_methods.rb +7 -0
  58. data/spec/fixtures/nested_methods.rb +8 -0
  59. data/spec/fixtures/program_1.rb +19 -0
  60. data/spec/fixtures/program_2.rb +25 -0
  61. data/spec/fixtures/program_3.rb +66 -0
  62. data/spec/fixtures/program_4.rb +1 -0
  63. data/spec/fixtures/single_class.rb +9 -0
  64. data/spec/fixtures/single_method.rb +3 -0
  65. data/spec/formatters/csv_spec.rb +37 -0
  66. data/spec/formatters/html_index_spec.rb +36 -0
  67. data/spec/formatters/html_spec.rb +48 -0
  68. data/spec/formatters/text_spec.rb +39 -0
  69. data/spec/parsed_file_spec.rb +67 -0
  70. data/spec/parsed_method_spec.rb +34 -0
  71. data/spec/spec_helper.rb +7 -0
  72. metadata +229 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1fc61ed986af62dcb63b684638a07316d1e6739b
4
- data.tar.gz: d542092af9a5d11f0fc038e2cba9851d17b346cf
3
+ metadata.gz: f928fc2a5aaf979b7d338ba06a0a588cd07cf98e
4
+ data.tar.gz: 3108a32bef9afb6a87a26023da2b7e5e751ab774
5
5
  SHA512:
6
- metadata.gz: 3535144b55af2c9e36e357abcf09022533b2d8ea69ab5ad6314d7168e350531d9c4cf099727ae3f45243947baafa3d1905a6a0b457f0e31d77dd70f7f04c3db1
7
- data.tar.gz: a98f9f76a609656ec6f4b2b6d0b3fdbb23f04b6989d644849fbc141243ecbe87722d9fe6e30139421cbc5372ab1def110c83efb49fdd7d66369597d2bb09f68c
6
+ metadata.gz: c9a8c882c5e080ae9612f7531cbac8037c703b3ca0a73206c07db9de3e94c16021c41a7f62add65a854cefda1d2c8d8746bb72ea08125a3e84875864cb66f75c
7
+ data.tar.gz: d7d3a8615db9efe5c88471b7f30dc6eedb5bf3e5caff532f0ff689abc2621a67ac9e02c12309b03d3f941ba4280ca41ff2da7f6faed5c0164f16ec7673624d7e
data/.gitignore CHANGED
@@ -7,7 +7,7 @@ Gemfile.lock
7
7
  InstalledFiles
8
8
  _yardoc
9
9
  coverage
10
- doc/
10
+ doc/*/
11
11
  lib/bundler/man
12
12
  pkg
13
13
  rdoc
@@ -20,3 +20,6 @@ tmp
20
20
  *.o
21
21
  *.a
22
22
  mkmf.log
23
+ .console_history
24
+ *.swp
25
+ coverage
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,13 @@
1
+ # CODE OF CONDUCT
2
+
3
+ This project has adopted version 0.4 of the [Contributor Covenant](https://github.com/bantik/contributor_covenant/).
4
+
5
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
6
+
7
+ If any participant in this project has issues or takes exception with a contribution, they are obligated to provide constructive feedback and never resort to personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ We promise to extend courtesy and respect to everyone involved in this project regardless of gender, gender identity, sexual orientation, ability or disability, ethnicity, religion, or level of experience.
data/README.md CHANGED
@@ -21,6 +21,7 @@ Or install it yourself as:
21
21
  TODO: Write usage instructions here
22
22
 
23
23
  ## Contributing
24
+ Please note that this project is released with a [Contributor Code of Conduct](https://gitlab.com/coraline/analyst/blob/master/CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
24
25
 
25
26
  1. Fork it ( https://github.com/[my-github-username]/analyst/fork )
26
27
  2. Create your feature branch (`git checkout -b my-new-feature`)
data/analyst.gemspec CHANGED
@@ -18,6 +18,18 @@ 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"
22
+ spec.add_dependency "poro_plus"
23
+ spec.add_dependency "haml"
24
+ spec.add_dependency "parser"
25
+ spec.add_dependency "rainbow"
26
+ spec.add_dependency "rouge"
27
+ spec.add_dependency "terminal-table"
28
+ spec.add_dependency "thor"
29
+
21
30
  spec.add_development_dependency "bundler", "~> 1.6"
22
31
  spec.add_development_dependency "rake"
32
+ spec.add_development_dependency "rspec"
33
+ spec.add_development_dependency "simplecov"
34
+
23
35
  end
@@ -0,0 +1,162 @@
1
+ require 'parser/current'
2
+
3
+ class Analyzer
4
+
5
+ CONDITIONALS = [:if, :or_asgn, :and_asgn, :or, :and]
6
+
7
+ attr_accessor :content, :class_name, :edges, :nodes, :exits
8
+
9
+ DEFAULT_CLASS_NAME = "Unknown"
10
+
11
+ def initialize(content)
12
+ self.content = content
13
+ self.edges = 0
14
+ self.nodes = 1
15
+ self.exits = 1
16
+ end
17
+
18
+ def complexity
19
+ return unless traverse(parsed)
20
+ self.edges - self.nodes + exits
21
+ end
22
+
23
+ def class_name
24
+ find_class(parsed) || DEFAULT_CLASS_NAME
25
+ end
26
+
27
+ def methods
28
+ @methods ||= methods_from(parsed)
29
+ end
30
+
31
+ def constants
32
+ @constants ||= constants_from(parsed)
33
+ end
34
+
35
+ def method_names
36
+ @method_names ||= method_names_from(parsed)
37
+ end
38
+
39
+ def extract_methods
40
+ @methods ||= methods_from(parsed)
41
+ end
42
+
43
+ def extract_class_name
44
+ return self.class_name if self.class_name && ! self.class_name.empty?
45
+ found = find_class(parsed)
46
+ self.class_name = ! found.empty? && found || DEFAULT_CLASS_NAME
47
+ end
48
+
49
+ private
50
+
51
+ def method_list
52
+ @method_list ||= method_names
53
+ end
54
+
55
+ def method_names_from(node, found=[])
56
+ return found unless node.respond_to?(:type)
57
+ if node.type == :def || node.type == :defs
58
+ name = node.loc.name
59
+ found << content[name.begin_pos..name.end_pos - 1].to_sym
60
+ end
61
+ node.children.each do |child|
62
+ method_names_from(child, found) if parent_node?(child)
63
+ end
64
+ found
65
+ end
66
+
67
+ def constants_from(node, found=[])
68
+ if node.type == :const
69
+ expression = node.loc.expression
70
+ found << content[expression.begin_pos..expression.end_pos - 1]
71
+ end
72
+ node.children.each do |child|
73
+ constants_from(child, found) if parent_node?(child)
74
+ end
75
+ found.reject{ |constant| constant == class_name }
76
+ end
77
+
78
+ def extract_references_from(node, found=[])
79
+ return found unless node && node.respond_to?(:type)
80
+ if node.type == :send
81
+ reference = node.loc.expression
82
+ found << node.children.last
83
+ end
84
+ node.children.each do |child|
85
+ extract_references_from(child, found)
86
+ end
87
+ found.select{|name| method_list.include?(name)}
88
+ end
89
+
90
+ def text_at(start_pos, end_pos)
91
+ content[start_pos..end_pos - 1]
92
+ end
93
+
94
+ def find_class(node)
95
+ return unless node && node.respond_to?(:type)
96
+ concat = []
97
+ if node.type == :module || node.type == :class
98
+ concat << text_at(node.loc.name.begin_pos, node.loc.name.end_pos)
99
+ end
100
+ concat << node.children.map{|child| find_class(child)}.compact
101
+ concat.flatten.select(&:present?).join('::')
102
+ end
103
+
104
+ def extend_graph
105
+ self.edges += 2
106
+ self.nodes += 2
107
+ self.exits += 1
108
+ end
109
+
110
+ def methods_from(node, methods=[])
111
+ if node.type == :def || node.type == :defs
112
+ name = node.loc.name
113
+ expression = node.loc.expression
114
+ type = case(node.type)
115
+ when :defs
116
+ :class
117
+ when :def
118
+ :instance
119
+ when :class
120
+ :none
121
+ end
122
+ methods << ParsedMethod.new(
123
+ name: content[name.begin_pos..name.end_pos - 1],
124
+ content: content[expression.begin_pos..expression.end_pos - 1],
125
+ type: type,
126
+ refs: extract_references_from(node)
127
+ )
128
+ end
129
+ node.children.each do |child|
130
+ if parent_node?(child)
131
+ methods_from(child, methods)
132
+ end
133
+ end
134
+ methods.reject{ |m| m.name.empty? }
135
+ end
136
+
137
+ def parent_node?(node)
138
+ node.respond_to?(:type) || node.respond_to?(:children)
139
+ end
140
+
141
+ def parse!
142
+ traverse(parsed) && complexity
143
+ end
144
+
145
+ def parsed
146
+ @parsed ||= Parser::CurrentRuby.parse(content)
147
+ end
148
+
149
+ def traverse(node, accumulator=[], extract_methods=false)
150
+ accumulator << node.type
151
+ extend_graph if CONDITIONALS.include?(node.type)
152
+ node.children.each do |child|
153
+ if parent_node?(child)
154
+ accumulator << child.type
155
+ traverse(child, accumulator)
156
+ end
157
+ end
158
+ accumulator
159
+ end
160
+
161
+ end
162
+
@@ -0,0 +1,42 @@
1
+ require 'thor'
2
+ require 'Analyst'
3
+
4
+ module Analyst
5
+
6
+ class CLI < Thor
7
+
8
+ desc_text = "Formats are text (default, to STDOUT), html, and csv. "
9
+ desc_text << "Example: fuku check foo/ -f html"
10
+
11
+ desc "check PATH_TO_FILE [-f FORMAT] [-t MAX_COMPLEXITY_ALLOWED]", desc_text
12
+ method_option :format, :type => :string, :default => 'text', :aliases => "-f"
13
+ method_option :threshold, :type => :numeric, :default => 0, :aliases => "-t"
14
+
15
+ def check(path="./")
16
+ parser = Analyst::Parser.new(
17
+ path,
18
+ formatter,
19
+ options['threshold']
20
+ )
21
+ parser.parse_files
22
+ parser.report
23
+ end
24
+
25
+ default_task :check
26
+
27
+ private
28
+
29
+ def formatter
30
+ case options['format']
31
+ when 'html'
32
+ Formatters::Html
33
+ when 'csv'
34
+ Formatters::Csv
35
+ else
36
+ Formatters::Text
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,24 @@
1
+ module Analyst
2
+
3
+ module EntityParser
4
+
5
+ class ClassRelation
6
+ attr_reader :type, :source, :target_class_name, :target
7
+
8
+ def initialize(type:, source:, target_class_name:)
9
+ @type = type
10
+ @source = source
11
+ @target_class_name = target_class_name
12
+ end
13
+
14
+ def resolve_target!(entity_list)
15
+ unless @target = entity_list.detect{ |entity| entity.full_name == self.target_class_name }
16
+ puts "WARNING: Couldn't find target: #{self.target_class_name}"
17
+ end
18
+ end
19
+
20
+ end
21
+
22
+ end
23
+ end
24
+
@@ -0,0 +1,92 @@
1
+ #TODO add == to association
2
+
3
+ module Analyst
4
+
5
+ module EntityParser
6
+ module Entities
7
+ class ActiveModel < Entity
8
+
9
+ ASSOCIATIONS = [:belongs_to, :has_one, :has_many, :has_and_belongs_to_many]
10
+
11
+ attr_reader :associations
12
+
13
+ def initialize
14
+ @associations = []
15
+ end
16
+
17
+ def handle_send(node)
18
+ target, method_name, *args = node.children
19
+ if ASSOCIATIONS.include?(method_name)
20
+ add_association(method_name, args)
21
+ end
22
+ end
23
+
24
+ # When a class is reopened, merge its associations
25
+ def merge_associations_from(klass)
26
+ klass.associations.each do |association|
27
+ associations << Association.new(
28
+ type: association.type,
29
+ source: self,
30
+ target_class: association.target_class
31
+ )
32
+ end
33
+ associations.uniq!
34
+ end
35
+
36
+ def name
37
+ const_node_array(ast.children.first).join('::')
38
+ end
39
+
40
+ def full_name
41
+ parent.full_name.empty? ? name : parent.full_name + '::' + name
42
+ end
43
+
44
+ private
45
+
46
+ def add_association(method_name, args)
47
+ target_class = value_from_hash_node(args.last, :class_name)
48
+ target_class ||= begin
49
+ symbol_node = args.first
50
+ symbol_name = symbol_node.children.first
51
+ symbol_name.pluralize.classify
52
+ end
53
+ association = Association.new(type: method_name, source: self, target_class: target_class)
54
+ associations << association
55
+ end
56
+
57
+ private
58
+
59
+ # takes a (const) node and returns an array specifying the fully-qualified
60
+ # constant name that it represents. ya know, so CoolModule::SubMod::SweetClass
61
+ # would be parsed to:
62
+ # (const
63
+ # (const
64
+ # (const nil :CoolModule) :SubMod) :SweetClass)
65
+ # and passing that node here would return [:CoolModule, :SubMod, :SweetClass]
66
+ def const_node_array(node)
67
+ return [] if node.nil?
68
+ raise "expected (const) node or nil, got (#{node.type})" unless node.type == :const
69
+ const_node_array(node.children.first) << node.children[1]
70
+ end
71
+
72
+ # Fetches value from hash node iff key is symbol and value is str
73
+ # Raises an exception if value is not str
74
+ # Returns nil if key is not found
75
+ def value_from_hash_node(node, key)
76
+ return unless node.type == :hash
77
+ pair = node.children.detect do |pair_node|
78
+ key_symbol_node = pair_node.children.first
79
+ key == key_symbol_node.children.first
80
+ end
81
+ if pair
82
+ value_node = pair.children.last
83
+ throw "Bad type. Expected (str), got (#{value_node.type})" unless value_node.type == :str
84
+ value_node.children.first
85
+ end
86
+ end
87
+
88
+ end
89
+
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,13 @@
1
+ module Analyst
2
+
3
+ module EntityParser
4
+ module Entities
5
+ class Empty < Entity
6
+ def full_name
7
+ ""
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,29 @@
1
+ module Analyst
2
+ module EntityParser
3
+ module Entities
4
+ class Entity
5
+
6
+ attr_reader :ast, :parent
7
+
8
+ def initialize(parent, ast)
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
+ def full_name
19
+ throw "Subclass must implement #full_name"
20
+ end
21
+
22
+ def inspect
23
+ "\#<#{self.class}:#{object_id} full_name=#{full_name}>"
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,16 @@
1
+ module Analyst
2
+
3
+ module EntityParser
4
+ module Entities
5
+ class Method < Entity
6
+ def name
7
+ ast.children.first.to_s
8
+ end
9
+ def full_name
10
+ parent.full_name + '#' + name
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,31 @@
1
+ module Analyst
2
+
3
+ module EntityParser
4
+ module Entities
5
+ class Module < Entity
6
+ def name
7
+ const_node_array(ast.children.first).join('::')
8
+ end
9
+ def full_name
10
+ parent.full_name.empty? ? name : parent.full_name + '::' + name
11
+ end
12
+
13
+ private
14
+
15
+ # takes a (const) node and returns an array specifying the fully-qualified
16
+ # constant name that it represents. ya know, so CoolModule::SubMod::SweetClass
17
+ # would be parsed to:
18
+ # (const
19
+ # (const
20
+ # (const nil :CoolModule) :SubMod) :SweetClass)
21
+ # and passing that node here would return [:CoolModule, :SubMod, :SweetClass]
22
+ def const_node_array(node)
23
+ return [] if node.nil?
24
+ raise "expected (const) node or nil, got (#{node.type})" unless node.type == :const
25
+ const_node_array(node.children.first) << node.children[1]
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ end
@@ -0,0 +1,33 @@
1
+ module Formatters
2
+
3
+ module Base
4
+
5
+ def self.included(klass)
6
+ klass.send(:attr_accessor, :file)
7
+ klass.send(:attr_accessor, :source)
8
+ klass.send(:attr_accessor, :output_directory)
9
+ end
10
+
11
+ def initialize(file, output_directory=nil, source="")
12
+ self.file = file
13
+ self.source = source
14
+ self.output_directory = output_directory
15
+ end
16
+
17
+ def filename
18
+ File.basename(self.file.path_to_file) + file_extension
19
+ end
20
+
21
+ def output_path
22
+ output_path = File.dirname(File.join(self.output_directory, self.file.path_to_file))
23
+ FileUtils.mkpath(output_path)
24
+ output_path
25
+ end
26
+
27
+ def path_to_results
28
+ File.join(output_path, filename)
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,43 @@
1
+ module Formatters
2
+
3
+ class Csv
4
+
5
+ include Formatters::Base
6
+
7
+ def self.has_index?
8
+ false
9
+ end
10
+
11
+ def self.writes_to_file_system?
12
+ true
13
+ end
14
+
15
+ def content
16
+ rows + "\r\n"
17
+ end
18
+
19
+ def export
20
+ begin
21
+ File.open(path_to_results, 'a') {|outfile| outfile.write(content)}
22
+ rescue Exception => e
23
+ puts "Unable to write output: #{e} #{e.backtrace}"
24
+ end
25
+ end
26
+
27
+ def file_extension
28
+ ".csv"
29
+ end
30
+
31
+ def path_to_results
32
+ File.join(output_directory, "results#{file_extension}")
33
+ end
34
+
35
+ def rows
36
+ file.methods.map do |method|
37
+ "#{file.path_to_file},#{file.class_name},#{method.name},#{method.complexity}"
38
+ end.join("\r\n")
39
+ end
40
+
41
+ end
42
+
43
+ end
@@ -0,0 +1,87 @@
1
+ require 'rouge'
2
+
3
+ module Formatters
4
+
5
+ class Html
6
+
7
+ include Formatters::Base
8
+
9
+ def self.has_index?
10
+ true
11
+ end
12
+
13
+ def self.writes_to_file_system?
14
+ true
15
+ end
16
+
17
+ def self.index_class
18
+ Formatters::HtmlIndex
19
+ end
20
+
21
+ def columns
22
+ ["class", "method", "complexity"]
23
+ end
24
+
25
+ def content
26
+ Haml::Engine.new(output_template).render(
27
+ Object.new, {
28
+ header: header,
29
+ rows: rows,
30
+ source_lines: preprocessed,
31
+ class_name: file.class_name,
32
+ complexity: file.complexity,
33
+ path_to_file: file.path_to_file,
34
+ date: Time.now.strftime("%Y/%m/%d"),
35
+ time: Time.now.strftime("%l:%M %P")
36
+ }
37
+ )
38
+ end
39
+
40
+ def export
41
+ begin
42
+ File.open(path_to_results, 'w') {|outfile| outfile.write(content)}
43
+ rescue Exception => e
44
+ puts "Unable to write output: #{e} #{e.backtrace}"
45
+ end
46
+ end
47
+
48
+ def formatter
49
+ Rouge::Formatters::HTML.new(line_numbers: true)
50
+ end
51
+
52
+ def header
53
+ columns.map{|col| "<th>#{col.titleize}</th>"}.join("\r\n")
54
+ end
55
+
56
+ def lexer
57
+ lexer = Rouge::Lexers::Ruby.new
58
+ end
59
+
60
+ def output_template
61
+ File.read(File.dirname(__FILE__) + "/templates/output.html.haml")
62
+ end
63
+
64
+ def preprocessed
65
+ formatter.format(lexer.lex(source))
66
+ end
67
+
68
+ def rows
69
+ i = 0
70
+ file.methods.inject([]) do |a, method|
71
+ i += 1
72
+ a << "<tr class='#{i % 2 == 1 ? 'even' : 'odd'}'>"
73
+ a << " <td>#{file.class_name}</td>"
74
+ a << " <td>#{method.name}</td>"
75
+ a << " <td>#{method.complexity}</td>"
76
+ a << "</tr>"
77
+ a
78
+ end.join("\r\n")
79
+ end
80
+
81
+ def file_extension
82
+ ".htm"
83
+ end
84
+
85
+ end
86
+
87
+ end