locat 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.ruby ADDED
@@ -0,0 +1,48 @@
1
+ ---
2
+ name: locat
3
+ version: 0.1.0
4
+ title: LOCat
5
+ summary: Lines of Code Attache
6
+ description: LOCat is a customizable Line-Of-Code metric system. LOC might not be the most useful metric in the universe but it still provide useful inforamtion and can be a lot of fun.
7
+ loadpath:
8
+ - lib
9
+ manifest: MANIFEST
10
+ requires:
11
+ - name: ansi
12
+ version: 0+
13
+ group: []
14
+
15
+ - name: detroit
16
+ version: 0+
17
+ group:
18
+ - build
19
+ - name: qed
20
+ version: 0+
21
+ group:
22
+ - test
23
+ conflicts: []
24
+
25
+ replaces: []
26
+
27
+ engine_check: []
28
+
29
+ organization: Rubyworks
30
+ contact: trans <transfire@gmail.com>
31
+ created: 2011-07-07
32
+ copyright: Copyright (c) 2011 Thomas Sawyer
33
+ licenses:
34
+ - BSD-2-Clause
35
+ authors:
36
+ - Thomas Sawyer
37
+ maintainers: []
38
+
39
+ resources:
40
+ home: http://rubyworks.github.com/locat
41
+ code: http://github.com/rubyworks/locat
42
+ docs: http://wiki.github.com/rubyworks/locat/docs/qed
43
+ wiki: http://wiki.github.com/rubyworks/locat
44
+ bugs: http://github.com/rubyworks/locat/issues
45
+ mail: http://groups.google.com/group/rubyworks-mailinglist
46
+ repositories:
47
+ public: git://github.com/rubyworks/locat.git
48
+ spec_version: 1.0.0
@@ -0,0 +1,33 @@
1
+ = COPYRIGHT NOTICES
2
+
3
+ == LOCat
4
+
5
+ Copyright:: (c) 2011 Thomas Sawyer, Rubyworks
6
+ License:: BSD-2-Clause
7
+
8
+ Copyright (c) 2011 Thomas Sawyer. All rights reserved.
9
+
10
+ Redistribution and use in source and binary forms, with or without modification, are
11
+ permitted provided that the following conditions are met:
12
+
13
+ 1. Redistributions of source code must retain the above copyright notice, this list of
14
+ conditions and the following disclaimer.
15
+
16
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list
17
+ of conditions and the following disclaimer in the documentation and/or other materials
18
+ provided with the distribution.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY Thomas Sawyer ``AS IS'' AND ANY EXPRESS OR IMPLIED
21
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
22
+ FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Thomas Sawyer OR
23
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
28
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
+
30
+ The views and conclusions contained in the software and documentation are those of the
31
+ authors and should not be interpreted as representing official policies, either expressed
32
+ or implied, of Thomas Sawyer.
33
+
@@ -0,0 +1,55 @@
1
+ = LOCAT
2
+
3
+
4
+ == DESCRIPTION
5
+
6
+ LOCat is a fancy LOC code analysis tool.
7
+
8
+
9
+ == RESOURCES
10
+
11
+ * {Homepage}[http://rubyworks.github.com/locat]
12
+ * {Development}[http://github.com/rubyworks/locat]
13
+ * {Issue Tracker}[http://github.com/rubyworks/locat/issues]
14
+ * {Mailing List}[http://groups.google.com/group/rubyworks-mailinglist]
15
+
16
+
17
+ == SYNOPSIS
18
+
19
+ Define a `.locat` Ruby script in your project, e.g.
20
+
21
+ match 'lib/**.rb' do |file, line|
22
+ case line
23
+ when /^\s*#/
24
+ 'Comment'
25
+ when /^\s*$/
26
+ 'Blank'
27
+ else
28
+ 'Code'
29
+ end
30
+ end
31
+
32
+ match 'test/**.rb' do |file, line|
33
+ case line
34
+ when /^\s*#/
35
+ 'Comment'
36
+ when /^\s*$/
37
+ 'Blank'
38
+ else
39
+ 'Test'
40
+ end
41
+ end
42
+
43
+ Then run `locat`, e.g.
44
+
45
+ $ locat -o locat.html
46
+
47
+
48
+ == COPYRIGHT
49
+
50
+ Copyright (c) 2011 Thomas Sawyer, Rubyworks
51
+
52
+ BSD 2-Clause License
53
+
54
+ See COPYING.rdoc for details.
55
+
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'locat'
3
+ LOCat.cli(*ARGV)
@@ -0,0 +1,14 @@
1
+ module LOCat
2
+
3
+ require 'json'
4
+ require 'optparse'
5
+ require 'fileutils'
6
+ require 'erb'
7
+
8
+ require 'locat/matcher'
9
+ require 'locat/counter'
10
+ require 'locat/template'
11
+ require 'locat/gitloc'
12
+ require 'locat/command'
13
+
14
+ end
@@ -0,0 +1,129 @@
1
+ module LOCat
2
+
3
+ #
4
+ def self.cli(*argv)
5
+ options = {}
6
+
7
+ OptionParser.new do |opt|
8
+ opt.on('-o', '--output FILE', 'output file') do |output|
9
+ options[:output] = output
10
+ end
11
+ opt.on('-j', '--json', 'output JSON formmated data') do
12
+ options[:format] = 'json'
13
+ end
14
+ opt.on('-y', '--yaml', 'output YAML formmated data') do
15
+ options[:format] = 'yaml'
16
+ end
17
+ opt.on('-c', '--config NAME', 'matcher configuraton') do |name|
18
+ options[:config] ||= []
19
+ options[:config] << name
20
+ end
21
+ opt.on('-D', '--debug', 'run in debug mode') do
22
+ $DEBUG = true
23
+ end
24
+ opt.on('-h', '--help', 'display this help message') do
25
+ puts opt
26
+ exit 0
27
+ end
28
+ end.parse!(argv)
29
+
30
+ options[:files] = argv
31
+
32
+ command = Command.new(options)
33
+ command.run
34
+ end
35
+
36
+ #
37
+ class Command
38
+
39
+ #
40
+ def initialize(options)
41
+ @files = nil
42
+ @output = nil
43
+ @format = 'highchart'
44
+ @config = default_config_files
45
+
46
+ options.each do |k,v|
47
+ send("#{k}=", v)
48
+ end
49
+ end
50
+
51
+ # Files to include in analysis.
52
+ attr_accessor :files
53
+
54
+ # The output format (json, yaml, highchart).
55
+ attr_accessor :format
56
+
57
+ # List of configuration files.
58
+ attr_accessor :config
59
+
60
+ # Output file.
61
+ attr_reader :output
62
+
63
+ # Set output file name.
64
+ def output=(file)
65
+ @output = file
66
+ end
67
+
68
+ #
69
+ def run
70
+ save
71
+ end
72
+
73
+ private
74
+
75
+ #
76
+ def default_config_files
77
+ Dir['.locat{,/*.rb}'].select{ |f| File.file?(f) }
78
+ end
79
+
80
+ #
81
+ def matcher
82
+ @matcher ||= Matcher.new(*config)
83
+ end
84
+
85
+ #
86
+ def counter
87
+ @counter ||= Counter.new(matcher, :files=>files)
88
+ end
89
+
90
+ #
91
+ def template
92
+ @template ||= Template.new(counter)
93
+ end
94
+
95
+ # Save.
96
+ def save
97
+ case format.downcase
98
+ when 'json'
99
+ json = counter.to_h.to_json
100
+ save_file(json)
101
+ when 'yaml'
102
+ yaml = counter.to_h.to_yaml
103
+ save_file(yaml)
104
+ else
105
+ html = template.render(format)
106
+ save_file(html)
107
+ end
108
+ end
109
+
110
+ #
111
+ def save_file(text)
112
+ if output
113
+ FileUtils.mkdir_p(File.dirname(output))
114
+ File.open(File.join(output), 'w') do |f|
115
+ f << text
116
+ end
117
+ else
118
+ puts text
119
+ end
120
+ end
121
+
122
+ #
123
+ def template_dir
124
+ File.dirname(__FILE__) + '/locat/template'
125
+ end
126
+
127
+ end
128
+
129
+ end
@@ -0,0 +1,155 @@
1
+ module LOCat
2
+
3
+ #
4
+ class Counter
5
+
6
+ #
7
+ def initialize(matcher, options)
8
+ @matcher = matcher
9
+
10
+ options.each do |k,v|
11
+ send("#{k}=", v)
12
+ end
13
+ end
14
+
15
+ #
16
+ attr :matcher
17
+
18
+ #
19
+ attr :files
20
+
21
+ #
22
+ def files=(files)
23
+ @files = files.map{ |f| Dir[f] }.flatten
24
+ end
25
+
26
+ #
27
+ def counts
28
+ @counts ||= (
29
+ table = Hash.new{ |h,k| h[k] = 0 }
30
+ matcher.each do |pattern, block|
31
+ files = resolve_files(pattern)
32
+ files.each do |file|
33
+ File.readlines(file).each do |line|
34
+ line = line.chomp("\n")
35
+ group = block.call(file, line)
36
+ if group
37
+ table[group.to_s] += 1
38
+ end
39
+ end
40
+ end
41
+ end
42
+ table
43
+ )
44
+ end
45
+
46
+ #
47
+ def total
48
+ @total = counts.values.inject(0){ |s,v| s+=v; s }
49
+ end
50
+
51
+ #
52
+ def loc
53
+ @loc ||= (
54
+ a = []
55
+ a << [nil , *counts.keys]
56
+ a << ['loc', *counts.values]
57
+ a
58
+ )
59
+ end
60
+
61
+ # Compute the ratios between each group,
62
+ # returning a two-dimensional array.
63
+ def ratio
64
+ @ratio ||= (
65
+ rcounts = counts.dup
66
+ rcounts['Total'] = total
67
+ r = Array.new(rcounts.size+1){ [] }
68
+ x = 1
69
+ rcounts.each do |type1, count1|
70
+ r[x][0] = type1
71
+ y = 1
72
+ rcounts.each do |type2, count2|
73
+ r[0][y] = type2
74
+ if count2 == 0 or count2 == 0
75
+ r[x][y] = 0
76
+ else
77
+ r[x][y] = (1000 * (count2.to_f / count1.to_f)).round.to_f / 1000.0
78
+ end
79
+ y += 1
80
+ end
81
+ x += 1
82
+ end
83
+ r
84
+ )
85
+ end
86
+
87
+ # Calculate the percentages.
88
+ def percent
89
+ @percent ||= (
90
+ a = []
91
+ a << [nil , *counts.keys]
92
+ a << ['%', *counts.values.map{ |f| 100 * f / total }]
93
+ a
94
+ )
95
+ end
96
+
97
+ #
98
+ def to_h
99
+ h = {}
100
+ h['loc'] = loc
101
+ h['pcnt'] = percent
102
+ h['ratio'] = ratio
103
+ h['scm'] = scm if scm?
104
+ h
105
+ end
106
+
107
+ #
108
+ #def to_json
109
+ # to_h.to_json
110
+ #end
111
+
112
+ #
113
+ def gitloc
114
+ @gitloc ||= GitLOC.new(matcher)
115
+ end
116
+
117
+ #
118
+ def scm?
119
+ File.directory?('.git')
120
+ end
121
+
122
+ #
123
+ def scm
124
+ if scm?
125
+ gitloc.timeline_table
126
+ else
127
+ nil
128
+ end
129
+ end
130
+
131
+ private
132
+
133
+ # Resolve file glob.
134
+ def resolve_files(pattern)
135
+ case pattern
136
+ when String
137
+ Dir['**/*'].select{ |path| File.fnmatch(pattern, path) }
138
+ when Regexp
139
+ Dir['**/*'].select{ |path| pattern =~ path }
140
+ else
141
+ []
142
+ end
143
+ #if File.directory?(glob)
144
+ # glob = File.join(glob, '**', '*')
145
+ #end
146
+ #if files.nil? or files.empty?
147
+ # Dir[glob].flatten
148
+ #else
149
+ # Dir[glob].flatten & files
150
+ #end
151
+ end
152
+
153
+ end
154
+
155
+ end
@@ -0,0 +1,139 @@
1
+ require 'rubygems'
2
+ #require 'active_support'
3
+ require 'grit'
4
+ require 'open-uri'
5
+ require 'fileutils'
6
+
7
+ module LOCat
8
+
9
+ # Based on `git-line-count.rb` by Tieg Zaharia
10
+ class GitLOC
11
+
12
+ include Grit
13
+
14
+ MAX_COMMITS = 1_000_000
15
+ OUTPUT_FILE = "gitloc.html"
16
+ #FILE_EXTENSION = /\.(rb|js)$/
17
+ #EXCLUDED_FILES = %w(files.js to.js exclude.js).map{ |str| Regexp.escape(str) }.join('|')
18
+ #EXCLUDED = /#{EXCLUDED_FILES}/i
19
+ PER_DAY = false # true = show 1-commit-per-day, false = show all commits
20
+ DATA_POINTS = 25
21
+
22
+ attr :matcher
23
+
24
+ attr :repo
25
+
26
+ #
27
+ def initialize(matcher)
28
+ @matcher = matcher
29
+ @repo = Repo.new(".")
30
+
31
+ @data_points = DATA_POINTS
32
+ @output_file = OUTPUT_FILE
33
+ #@file_extension = FILE_EXTENSION
34
+ #@exclude = EXCLUDED
35
+ @per_day = PER_DAY
36
+ end
37
+
38
+ #
39
+ def title
40
+ "'#{repo.head.name}' branch JavaScript LOC #{@per_day ? 'per day' : 'per commit'}"
41
+ end
42
+
43
+ # Count lines by groups.
44
+ def recursive_loc_count(dir, tree_or_blob, total_count=nil)
45
+ total_count ||= Hash.new{|h,k| h[k]=0 }
46
+ if tree_or_blob.is_a?(Grit::Tree) # directory
47
+ tree_or_blob.contents.each do |tob|
48
+ dname = dir ? File.join(dir, tree_or_blob.name) : tree_or_blob.name
49
+ recursive_loc_count(dname, tob, total_count)
50
+ end
51
+ elsif tree_or_blob.is_a?(Grit::Blob) # file
52
+ file = dir ? File.join(dir, tree_or_blob.name) : tree_or_blob.name
53
+ matcher.each do |glob, block|
54
+ if File.fnmatch?(glob, file)
55
+ tree_or_blob.data.lines.each do |line|
56
+ group = block.call(file, line)
57
+ if group
58
+ total_count[group] += 1
59
+ end
60
+ #total_count['Total'] += 1
61
+ end
62
+ end
63
+ end
64
+ else
65
+ # what is it then?
66
+ end
67
+ total_count
68
+ end
69
+
70
+ #
71
+ def commits_with_loc
72
+ @commits_with_loc ||= (
73
+ table = []
74
+ total_count = Hash.new{|h,k|h[k]=0} # gets reset every commit
75
+ current_date = nil # gets reset every commit
76
+
77
+ size = repo.commits('master', MAX_COMMITS).size
78
+ mod = size < @data_points ? 1 : (size / @data_points).round
79
+
80
+ # puts repo.commits[0].methods.sort.join(', ')
81
+ repo.commits('master', MAX_COMMITS).each_with_index do |commit, index|
82
+ next unless index % mod == 0
83
+
84
+ total_count = Hash.new{|h,k|h[k]=0}
85
+ this_date = commit.committed_date.to_date
86
+ if !@per_day || (@per_day && this_date != current_date)
87
+ # Record this commit as end-of-day commit
88
+ current_date = this_date
89
+ commit.tree.contents.each do |tob|
90
+ recursive_loc_count(nil, tob, total_count)
91
+ end
92
+ table << {
93
+ :date => commit.committed_date.to_datetime,
94
+ :id => commit.id,
95
+ :loc => total_count
96
+ }
97
+ else
98
+ # The day this commits falls on has already been recorded
99
+ end
100
+ end
101
+
102
+ table.reverse
103
+ )
104
+ end
105
+
106
+ #
107
+ def timeline_table
108
+ #mod = 7
109
+ th = [nil]
110
+ commits_with_loc.each_with_index do |commit, index|
111
+ #if index % mod == 0 || index == commits_with_loc.size - 1
112
+ # th << commit[:date].strftime("%-y %-m %-d")
113
+ #else
114
+ th << commit[:date].strftime("%y %m %d")
115
+ #end
116
+ end
117
+ tg = []
118
+ groups = []
119
+ commits_with_loc.each do |commit|
120
+ groups = groups | commit[:loc].keys
121
+ end
122
+ groups.each_with_index do |g, i|
123
+ tg[i] = [g]
124
+ end
125
+ tt = ['Total']
126
+ commits_with_loc.each do |commit|
127
+ sum = 0
128
+ groups.each_with_index do |g, i|
129
+ tg[i] << commit[:loc][g]
130
+ sum += commit[:loc][g]
131
+ end
132
+ tt << sum
133
+ end
134
+ [th] + tg + [tt]
135
+ end
136
+
137
+ end
138
+
139
+ end