locat 0.1.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.
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