snuffle 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6b33ac8a8524d501103f6b30117995298fab5060
4
+ data.tar.gz: 9f9e2c6e58f44b28fad06aaf7645ec213b6466aa
5
+ SHA512:
6
+ metadata.gz: 43437ee9b3eb9fae5729d66253d082ecb508d29e4d8e28559fd75de58d8ed51b8da347cdcef25e5ff9a456a96052a1acffc4ad9a46054254b2135dda540cb60a
7
+ data.tar.gz: 9f1d620ee9123654237e532c3236da8493e6e492f4dcfe791de39746438f126a17017c6c6ea5d81a146e2bc9a9b68bea52297fe5e5f2a89d46681f6e75ffa152
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ .console_history
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
@@ -0,0 +1,12 @@
1
+ # Contributor Code of Conduct
2
+ ## Version 0.4
3
+
4
+ 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.
5
+
6
+ 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.
7
+
8
+ 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.
9
+
10
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
11
+
12
+ 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/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in snuffle.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Bantik
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # Snuffle
2
+
3
+ Snuffle analyzes source code to identify "data clumps", clusters of attributes
4
+ that are often used together. It uses this analysis to propose objects that
5
+ may be extracted from a given class.
6
+
7
+ ## TODO
8
+
9
+ * Ignore data clumps called in "loose" class methods (e.g. attr_accessor)
10
+ * Match on string concatenation
11
+ * Consider weighting based on match type
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ gem 'snuffle'
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install snuffle
26
+
27
+ ## Usage
28
+
29
+ $ snuffle check example.rb
30
+
31
+ +----------------------------+------------+-----------------------------+
32
+ | Filename | Host Class | Candidate Object Attributes |
33
+ +----------------------------+------------+-----------------------------+
34
+ | example.rb | Customer | company_name, customer_name |
35
+ +----------------------------+------------+-----------------------------+
36
+
37
+ ## Contributing
38
+
39
+ Please note that this project is released with a [Contributor Code of Conduct](https://gitlab.com/coraline/snuffle/blob/master/CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
40
+
41
+ 1. Fork the project
42
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
43
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
44
+ 4. Push to the branch (`git push origin my-new-feature`)
45
+ 5. Create a new Merge Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/snuffle ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'snuffle/cli'
3
+ Snuffle::CLI.start
data/lib/snuffle.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'securerandom'
2
+ require 'ephemeral'
3
+ require 'poro_plus'
4
+ require 'fileutils'
5
+ require 'haml'
6
+
7
+ require_relative "snuffle/version"
8
+ require_relative "snuffle/cohort"
9
+ require_relative "snuffle/cli"
10
+ require_relative "snuffle/formatters/base"
11
+ require_relative "snuffle/formatters/csv"
12
+ require_relative "snuffle/formatters/html"
13
+ require_relative "snuffle/formatters/html_index"
14
+ require_relative "snuffle/formatters/text"
15
+ require_relative "snuffle/line_of_code"
16
+ require_relative "snuffle/node"
17
+ require_relative "snuffle/source_file"
18
+ require_relative "snuffle/summary"
19
+ require_relative "snuffle/elements/hash"
20
+ require_relative "snuffle/util/histogram"
21
+
22
+ module Snuffle
23
+ end
@@ -0,0 +1,74 @@
1
+ require 'thor'
2
+ require 'snuffle'
3
+
4
+ module Snuffle
5
+
6
+ class CLI < Thor
7
+
8
+ desc_text = "Formats are text (default, to STDOUT), html, and csv. "
9
+ desc_text << "Example: snuffle 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
+
14
+ def check(path="./")
15
+ summaries = []
16
+ file_list(path).each do |path_to_file|
17
+ summary = Snuffle::SourceFile.new(path_to_file: path_to_file).summary
18
+ report(summary, summary.source)
19
+ summaries << summary
20
+ end
21
+ create_html_index(summaries)
22
+ puts results_files.join("\n")
23
+ end
24
+
25
+ default_task :check
26
+
27
+ attr_accessor :last_file
28
+
29
+ private
30
+
31
+ def file_list(start_file)
32
+ if File.directory?(start_file)
33
+ return Dir.glob(File.join(start_file, "**", "*")).select{|n| n =~ /\.rb$/}
34
+ else
35
+ return [start_file]
36
+ end
37
+ end
38
+
39
+ def report(summary, source)
40
+ text_report(summary)
41
+ cvs_report(summary)
42
+ html_report(summary, source)
43
+ end
44
+
45
+ def create_html_index(summaries)
46
+ return unless options['format'] == 'html'
47
+ results_files << Snuffle::Formatters::HtmlIndex.new(summaries).export
48
+ end
49
+
50
+ def cvs_report(summary)
51
+ return unless options['format'] == 'csv'
52
+ return unless summary.cohorts.count > 0
53
+ results_files << Snuffle::Formatters::Csv.new(summary).export
54
+ end
55
+
56
+ def html_report(summary, source)
57
+ return unless options['format'] == 'html'
58
+ return unless summary.cohorts.count > 0
59
+ results_files << Snuffle::Formatters::Html.new(summary, source).export
60
+ end
61
+
62
+ def text_report(summary)
63
+ return unless options['format'] == 'text'
64
+ puts
65
+ puts Snuffle::Formatters::Text.new(summary).export
66
+ end
67
+
68
+ def results_files
69
+ @results_files ||= []
70
+ end
71
+
72
+ end
73
+
74
+ end
@@ -0,0 +1,42 @@
1
+ module Snuffle
2
+
3
+ class Cohort
4
+
5
+ include PoroPlus
6
+ attr_accessor :element, :neighbors, :line_numbers
7
+
8
+ def self.from(nodes)
9
+ cohorts = Element::Hash.materialize(nodes.hashes.to_a).inject([]) do |cohorts, element|
10
+ cohort = Cohort.new(element: element, line_numbers: element.node.line_numbers )
11
+ cohorts << cohort if cohort.values.count > 1 && cohort.near_neighbors.count > 0
12
+ cohorts
13
+ end
14
+ end
15
+
16
+ def has_near_neighbors?
17
+ near_neighbors.present?
18
+ end
19
+
20
+ def near_neighbors
21
+ @near_neighbors ||= neighbors.select{ |n| (n.values & values).size == values.size }
22
+ end
23
+
24
+ def neighbors
25
+ @neighbors ||= [element.node.siblings - [self.element.node]].flatten.map{|sibling| Element::Hash.materialize([sibling]).first}
26
+ end
27
+
28
+ def values
29
+ @values ||= self.element.values
30
+ end
31
+
32
+ def neighbor
33
+ Struct.new(:element, :distance)
34
+ end
35
+
36
+ def distance(primary_matrix, token_matrix)
37
+ Snuffle::Util::Correlation.distance(primary_matrix, token_matrix)
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,37 @@
1
+ module Snuffle
2
+
3
+ module Element
4
+
5
+ class Hash
6
+
7
+ attr_accessor :node
8
+
9
+ def self.materialize(nodes=[])
10
+ nodes.each.map{|hash_node| new(hash_node) }.select{|h| h.pairs.present?}
11
+ end
12
+
13
+ def initialize(node)
14
+ self.node = node
15
+ end
16
+
17
+ def pairs
18
+ @pairs ||= keys.zip(values).inject({}){|hash, pair| hash[pair[0]] = pair[1]; hash}
19
+ end
20
+
21
+ def keys
22
+ node.children.map{ |child| child.children.first && child.children.first.name }
23
+ end
24
+
25
+ def values
26
+ node.children.map{ |child| child.children.last && child.children.last.name }.map(&:to_s).sort
27
+ end
28
+
29
+ def inspect
30
+ pairs
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -0,0 +1,57 @@
1
+ module Snuffle
2
+
3
+ module Formatters
4
+
5
+ module Base
6
+
7
+ def self.included(klass)
8
+ klass.send(:attr_accessor, :summary)
9
+ klass.send(:attr_accessor, :source)
10
+ end
11
+
12
+ def initialize(summary, source="")
13
+ self.summary = summary
14
+ self.source = source
15
+ end
16
+
17
+ def content
18
+ [header, rows, footer].flatten.join("\r\n")
19
+ end
20
+
21
+ def columns
22
+ ["filename", "host class", "candidate object attributes", "source line numbers"]
23
+ end
24
+
25
+ def root_path
26
+ "doc/snuffle"
27
+ end
28
+
29
+ def output_path
30
+ FileUtils.mkpath(root_path)
31
+ root_path
32
+ end
33
+
34
+ def path_to_results
35
+ "#{output_path}/#{filename}"
36
+ end
37
+
38
+ def filename
39
+ base = summary.class_filename
40
+ base + file_extension
41
+ end
42
+
43
+ def file_extension
44
+ ""
45
+ end
46
+
47
+ def export
48
+ outfile = File.open("#{path_to_results}", 'w')
49
+ outfile.write(content)
50
+ outfile.close
51
+ path_to_results
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,31 @@
1
+ module Snuffle
2
+ module Formatters
3
+
4
+ class Csv
5
+
6
+ include Formatters::Base
7
+
8
+ def header
9
+ columns.join(',')
10
+ end
11
+
12
+ def rows
13
+ # summary.object_candidates.map do |candidate|
14
+ # [summary.path_to_file, summary.class_name, "##{candidate.join(" #")}"].join(',')
15
+ # end
16
+ summary.cohorts.group_by{|c| c.values}.map do |cohort|
17
+ [summary.path_to_file, summary.class_name, cohort[0].join("; "), cohort[1].map(&:line_numbers).join("; ")].join(',')
18
+ end
19
+ end
20
+
21
+ def footer
22
+ end
23
+
24
+ def file_extension
25
+ ".csv"
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,48 @@
1
+ require 'rouge'
2
+
3
+ module Snuffle
4
+ module Formatters
5
+
6
+ class Html
7
+
8
+ include Formatters::Base
9
+
10
+ def formatter
11
+ Rouge::Formatters::HTML.new(css_class: 'highlight', line_numbers: true)
12
+ end
13
+
14
+ def lexer
15
+ lexer = Rouge::Lexers::Ruby.new
16
+ end
17
+
18
+ def content
19
+ Haml::Engine.new(output_template).render(
20
+ Object.new, {
21
+ summary: summary,
22
+ source_lines: preprocessed,
23
+ date: Time.now.strftime("%Y/%m/%d"),
24
+ time: Time.now.strftime("%l:%M %P")
25
+ }
26
+ )
27
+ end
28
+
29
+ def output_template
30
+ File.read(File.dirname(__FILE__) + "/templates/output.html.haml")
31
+ end
32
+
33
+ def preprocessed
34
+ formatter.format(lexer.lex(source))
35
+ end
36
+
37
+ def root_path
38
+ "doc/snuffle/source"
39
+ end
40
+
41
+ def file_extension
42
+ ".htm"
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+ end