countloc 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/{LICENSE.txt → LICENSE} +0 -0
- data/README +90 -0
- data/lib/countloc.rb +288 -134
- data/setup.rb +1585 -0
- data/test/tc_ruby.rb +2 -0
- metadata +5 -4
- data/README.txt +0 -74
data/{LICENSE.txt → LICENSE}
RENAMED
File without changes
|
data/README
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
== Contents
|
2
|
+
|
3
|
+
* Overview
|
4
|
+
* Download
|
5
|
+
* Installation
|
6
|
+
* Usage
|
7
|
+
* Release Notes
|
8
|
+
|
9
|
+
== Overview
|
10
|
+
CountLOC is a utility program, implemented in Ruby that provides support for
|
11
|
+
generating LOC metrics for source code.
|
12
|
+
|
13
|
+
Initial releases will support counting lines of code in Ruby source code.
|
14
|
+
Subsequent releases will add support for other programming languages such as
|
15
|
+
Python, C, C++, C#, Java, Perl, etc. ...
|
16
|
+
|
17
|
+
== Download
|
18
|
+
The latest release can be downloaded from: http://rubyforge.org/frs/?group_id=7555&release_id=29931
|
19
|
+
|
20
|
+
== Installation
|
21
|
+
CountLOC is packaged as a Ruby gem and as a .zip file.
|
22
|
+
|
23
|
+
.gem:
|
24
|
+
% gem install countloc-x.y.z.gem
|
25
|
+
|
26
|
+
.zip:
|
27
|
+
% unzip countloc-x.y.z.zip
|
28
|
+
% cd countloc-x.y.z
|
29
|
+
% ruby setup.rb install
|
30
|
+
|
31
|
+
.tgz:
|
32
|
+
% tar zxf countloc-x.y.z.tgz
|
33
|
+
% cd countloc-x.y.z
|
34
|
+
% ruby setup.rb install
|
35
|
+
|
36
|
+
== Usage
|
37
|
+
For full listing of options and usage:
|
38
|
+
% countloc.rb --help
|
39
|
+
|
40
|
+
To get the LOC metrics for a single Ruby file:
|
41
|
+
% countloc.rb some_file.rb
|
42
|
+
|
43
|
+
To get the LOC metrics for multiple Ruby files:
|
44
|
+
% countloc.rb some_file.rb some_other_file.rb
|
45
|
+
|
46
|
+
To get the LOC metrics for all Ruby files in the current directory:
|
47
|
+
% countloc.rb *.rb
|
48
|
+
or
|
49
|
+
% countloc.rb .
|
50
|
+
|
51
|
+
To get the LOC metrics for all Ruby files in a directory tree with recursion
|
52
|
+
into subdirectories:
|
53
|
+
% countloc.rb -r .
|
54
|
+
|
55
|
+
To export the results to a html file:
|
56
|
+
% countloc.rb -r --html countloc.html .
|
57
|
+
|
58
|
+
To export the results to a csv file:
|
59
|
+
% countloc.rb -r --csv countloc.csv .
|
60
|
+
|
61
|
+
|
62
|
+
== Release Notes
|
63
|
+
=== Release 0.2.0:
|
64
|
+
Features
|
65
|
+
* Added support for exporting results in csv and html format.
|
66
|
+
* Improved error handling - e.g. incorrect/missing command line options, file errors, etc.
|
67
|
+
* Added setup.rb for ease of installation from the .zip/.tgz release formats.
|
68
|
+
* Added CountLOC module to encapsulate methods and classes.
|
69
|
+
|
70
|
+
Bugfixes:
|
71
|
+
* None
|
72
|
+
|
73
|
+
=== Release 0.1.0:
|
74
|
+
Features
|
75
|
+
* Added support for processing multiple files at once.
|
76
|
+
* Added support for processing all .rb files in a directory.
|
77
|
+
* Added support for recursing into directory trees.
|
78
|
+
* Improved formatting of output.
|
79
|
+
|
80
|
+
Bugfixes:
|
81
|
+
* #23380 - Not suppressing path information in the program name in the usage string.
|
82
|
+
* #23379 - Incorrectly counting a # inside a single quoted string as a comment.
|
83
|
+
|
84
|
+
=== Release 0.0.1:
|
85
|
+
Features
|
86
|
+
* Initial release.
|
87
|
+
* Supports generating LOC metrics for Ruby source code for a single
|
88
|
+
file at a time.
|
89
|
+
* Handles multi-line and single line comments.
|
90
|
+
* Handles mixed lines of both code and comments.
|
data/lib/countloc.rb
CHANGED
@@ -23,149 +23,283 @@
|
|
23
23
|
|
24
24
|
|
25
25
|
require 'optparse'
|
26
|
+
require 'time'
|
26
27
|
|
27
|
-
|
28
|
+
#
|
29
|
+
# CountLOC module that encapsulates the methods and classes used to gather
|
30
|
+
# code metrics.
|
31
|
+
#
|
32
|
+
module CountLOC
|
28
33
|
|
29
|
-
|
30
|
-
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
class
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
34
|
+
VERSION = '0.2.0'
|
35
|
+
|
36
|
+
# Class that gathers the metrics.
|
37
|
+
#
|
38
|
+
# This class design & implementation is based heavily on Stefan Lang's
|
39
|
+
# ScriptLines class from the "Ruby Cookbook" - Recipe 19.5 "Gathering
|
40
|
+
# Statistics About Your Code".
|
41
|
+
class LineCounter
|
42
|
+
attr_reader :name
|
43
|
+
attr_accessor :code, :comments, :blank, :lines
|
44
|
+
|
45
|
+
SINGLE_LINE_FULL_PATTERN = /^\s*#/
|
46
|
+
SINGLE_LINE_MIXED_PATTERN = /#/
|
47
|
+
MULTI_LINE_BEGIN_PATTERN = /=begin(\s|$)/
|
48
|
+
MULTI_LINE_END_PATTERN = /=end(\s|$)/
|
49
|
+
BLANK_LINE_PATTERN = /^\s*$/
|
50
|
+
|
51
|
+
LINE_FORMAT = '%8s %8s %8s %8s %12s %s'
|
52
|
+
|
53
|
+
#
|
54
|
+
# Generate a string that contains the column headers for the metrics
|
55
|
+
# printed with to_s
|
56
|
+
#
|
57
|
+
def self.headline
|
58
|
+
sprintf LINE_FORMAT, "LOC", "COMMENTS", "BLANK", "LINES", "CODE:COMMENT", "FILE"
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# Return an array containing the column names for the counters collected.
|
63
|
+
#
|
64
|
+
def self.columnNames
|
65
|
+
self.headline.split(' ', 6)
|
66
|
+
end
|
67
|
+
|
68
|
+
def initialize(name)
|
69
|
+
@name = name
|
70
|
+
@code = 0
|
71
|
+
@comments = 0
|
72
|
+
@blank = 0
|
73
|
+
@lines = 0
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# Iterates over all the lines in io (io might be a file or a string),
|
78
|
+
# analyzes them and appropriately increases the counter attributes.
|
79
|
+
#
|
80
|
+
def read(io)
|
81
|
+
in_multiline_comment = false
|
82
|
+
io.each do |line|
|
83
|
+
@lines += 1
|
84
|
+
|
85
|
+
# Process the line to avoid matching comment characters within quoted
|
86
|
+
# strings or regular expressions.
|
87
|
+
line.gsub!(/\'.*?\'/, "X") # Single quoted string
|
88
|
+
line.gsub!(/\".*?\"/, "X") # Double quoted string
|
89
|
+
line.gsub!(/\/.*?\//, "X") # Regular expression
|
90
|
+
|
91
|
+
case line
|
92
|
+
when MULTI_LINE_BEGIN_PATTERN
|
93
|
+
in_multiline_comment = true
|
94
|
+
@comments += 1
|
95
|
+
when MULTI_LINE_END_PATTERN
|
96
|
+
in_multiline_comment = false
|
97
|
+
@comments += 1
|
98
|
+
when BLANK_LINE_PATTERN
|
99
|
+
@blank += 1
|
100
|
+
when SINGLE_LINE_FULL_PATTERN
|
101
|
+
@comments += 1
|
102
|
+
when SINGLE_LINE_MIXED_PATTERN
|
103
|
+
@comments += 1
|
104
|
+
@code += 1
|
105
|
+
else
|
106
|
+
if in_multiline_comment
|
107
|
+
@comments += 1
|
108
|
+
else
|
109
|
+
@code += 1
|
110
|
+
end # if
|
111
|
+
end # case
|
112
|
+
end # read
|
113
|
+
end # class LineCounter
|
114
|
+
|
115
|
+
#
|
116
|
+
# Get a new LineCounter instance whose counters hold the sum of self
|
117
|
+
# and other.
|
118
|
+
#
|
119
|
+
def +(other)
|
120
|
+
sum = self.dup
|
121
|
+
sum.code += other.code
|
122
|
+
sum.comments += other.comments
|
123
|
+
sum.blank += other.blank
|
124
|
+
sum.lines += other.lines
|
125
|
+
return sum
|
126
|
+
end
|
127
|
+
|
128
|
+
#
|
129
|
+
# Get a formatted string containing all counter numbers and the name of
|
130
|
+
# this instance.
|
131
|
+
#
|
132
|
+
def to_s
|
133
|
+
codeCommentRatio = (sprintf "%0.2f", @code.to_f/@comments if @comments > 0) || '--'
|
134
|
+
sprintf LINE_FORMAT, @code, @comments, @blank, @lines, codeCommentRatio, @name
|
135
|
+
end
|
136
|
+
|
137
|
+
#
|
138
|
+
# Get a formatted string containing all counter numbers and the name of
|
139
|
+
# this instance.
|
140
|
+
#
|
141
|
+
def to_a
|
142
|
+
self.to_s.split(' ', 6)
|
143
|
+
end
|
43
144
|
|
44
|
-
|
145
|
+
end
|
45
146
|
|
46
147
|
#
|
47
|
-
#
|
48
|
-
# printed with to_s
|
148
|
+
# Container class to store results from each file.
|
49
149
|
#
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
150
|
+
class Results < Array
|
151
|
+
|
152
|
+
# Return a string containing all the results.
|
153
|
+
def to_s
|
154
|
+
str = LineCounter.headline + "\n"
|
155
|
+
self.each { |result| str += result.to_s + "\n"}
|
156
|
+
str
|
157
|
+
end
|
158
|
+
|
159
|
+
# Return a string containing all the results in csv format.
|
160
|
+
# The first row is a header row of the column names.
|
161
|
+
# Each subsequent row corresponds to a result from the results array.
|
162
|
+
def to_csv
|
163
|
+
csvString = LineCounter.columnNames.join(',') + "\n"
|
164
|
+
self.each { |result| csvString += result.to_a.join(',') + "\n"}
|
165
|
+
csvString
|
166
|
+
end
|
167
|
+
|
168
|
+
# Return a string containing the results formatted as a html table.
|
169
|
+
# The first row is a header row of the column names.
|
170
|
+
# Each subsequent row corresponds to a result from the results array.
|
171
|
+
def to_html
|
172
|
+
htmlString = %{<table border="1" cellspacing="0" cellpadding="2">}
|
173
|
+
htmlString += %{<tr>}
|
174
|
+
LineCounter.columnNames.each { |name| htmlString += %{<th>#{name}</th>} }
|
175
|
+
htmlString += %{</tr>}
|
176
|
+
self.each do |result|
|
177
|
+
htmlString += %{<tr>}
|
178
|
+
result.to_a.each { |cell| htmlString += %{<td>#{cell}</td> } }
|
179
|
+
htmlString += %{</tr>}
|
180
|
+
end
|
181
|
+
htmlString += %{</table>}
|
182
|
+
htmlString += %{<p><em>Generated by } +
|
183
|
+
%{<a href="http://countloc.rubyforge.org">countloc</a> version #{VERSION} } +
|
184
|
+
%{on #{Time.now.asctime}</em></p>}
|
185
|
+
end
|
186
|
+
|
60
187
|
end
|
61
188
|
|
62
|
-
#
|
63
|
-
# Iterates over all the lines in io (io might be a file or a string),
|
64
|
-
# analyzes them and appropriately increases the counter attributes.
|
65
|
-
#
|
66
|
-
def read(io)
|
67
|
-
in_multiline_comment = false
|
68
|
-
io.each do |line|
|
69
|
-
@lines += 1
|
70
|
-
|
71
|
-
# Process the line to avoid matching comment characters within quoted
|
72
|
-
# strings or regular expressions.
|
73
|
-
line.gsub!(/\'.*?\'/, "X") # Single quoted string
|
74
|
-
line.gsub!(/\".*?\"/, "X") # Double quoted string
|
75
|
-
line.gsub!(/\/.*?\//, "X") # Regular expression
|
76
|
-
|
77
|
-
case line
|
78
|
-
when MULTI_LINE_BEGIN_PATTERN
|
79
|
-
in_multiline_comment = true
|
80
|
-
@comments += 1
|
81
|
-
when MULTI_LINE_END_PATTERN
|
82
|
-
in_multiline_comment = false
|
83
|
-
@comments += 1
|
84
|
-
when BLANK_LINE_PATTERN
|
85
|
-
@blank += 1
|
86
|
-
when SINGLE_LINE_FULL_PATTERN
|
87
|
-
@comments += 1
|
88
|
-
when SINGLE_LINE_MIXED_PATTERN
|
89
|
-
@comments += 1
|
90
|
-
@code += 1
|
91
|
-
else
|
92
|
-
if in_multiline_comment
|
93
|
-
@comments += 1
|
94
|
-
else
|
95
|
-
@code += 1
|
96
|
-
end # if
|
97
|
-
end # case
|
98
|
-
end # read
|
99
|
-
end # class LineCounter
|
100
|
-
|
101
189
|
#
|
102
|
-
#
|
103
|
-
# and other.
|
190
|
+
# Class that writes to the Console
|
104
191
|
#
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
sum.lines += other.lines
|
111
|
-
return sum
|
192
|
+
class ConsoleWriter
|
193
|
+
# Write the data to the console
|
194
|
+
def write(data)
|
195
|
+
puts data
|
196
|
+
end
|
112
197
|
end
|
113
|
-
|
198
|
+
|
114
199
|
#
|
115
|
-
#
|
116
|
-
# this instance.
|
200
|
+
# Class that writes to a csv file
|
117
201
|
#
|
118
|
-
|
119
|
-
|
202
|
+
class CsvFileWriter
|
203
|
+
|
204
|
+
def initialize(filename)
|
205
|
+
@filename = filename
|
206
|
+
end
|
207
|
+
|
208
|
+
# Write the csv file, including the data contents (via
|
209
|
+
# the to_csv method on the data)
|
210
|
+
def write(data)
|
211
|
+
begin
|
212
|
+
File.open(@filename, "wb") { |file| file.puts data.to_csv }
|
213
|
+
rescue
|
214
|
+
puts "Error: " + $!
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
120
218
|
end
|
121
|
-
|
122
|
-
end
|
123
219
|
|
124
|
-
#
|
125
|
-
#
|
126
|
-
#
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
220
|
+
#
|
221
|
+
# Class that writes to a html file
|
222
|
+
#
|
223
|
+
class HtmlFileWriter
|
224
|
+
|
225
|
+
def initialize(filename)
|
226
|
+
@filename = filename
|
227
|
+
end
|
228
|
+
|
229
|
+
# Write the html file, including the data contents (via
|
230
|
+
# the to_html method on the data)
|
231
|
+
def write(data)
|
232
|
+
begin
|
233
|
+
File.open(@filename, "w") { |file| file.puts data.to_html }
|
234
|
+
rescue
|
235
|
+
puts "Error: " + $!
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
141
239
|
end
|
142
240
|
|
143
|
-
#
|
144
|
-
files
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
241
|
+
#
|
242
|
+
# Generates LOC metrics for the specified files and sends the results to the console.
|
243
|
+
#
|
244
|
+
def countloc(files, options = nil)
|
245
|
+
|
246
|
+
# Setup the output writers based on the options
|
247
|
+
writers = [ConsoleWriter.new]
|
248
|
+
writers << CsvFileWriter.new(options.csvFilename) if options.csv
|
249
|
+
writers << HtmlFileWriter.new(options.htmlFilename) if options.html
|
250
|
+
|
251
|
+
# Expand directories into the appropriate file lists
|
252
|
+
dirs = files.select { |filename| File.directory?(filename) }
|
253
|
+
if dirs.size > 0
|
254
|
+
recursePattern = ("**" if options.recurse) || ""
|
255
|
+
files -= dirs
|
256
|
+
files += dirs.collect { |dirname| Dir.glob(File.join(dirname, recursePattern, "*.rb"))}.flatten
|
257
|
+
end
|
258
|
+
|
259
|
+
# Sum will keep the running total
|
260
|
+
sum = LineCounter.new("TOTAL")
|
261
|
+
|
262
|
+
# Container to hold the results
|
263
|
+
results = Results.new
|
264
|
+
|
265
|
+
# Generate metrics for each file
|
266
|
+
files.each do |filename|
|
267
|
+
begin
|
268
|
+
File.open(filename) do |file|
|
269
|
+
counter = LineCounter.new(filename)
|
270
|
+
counter.read(file)
|
271
|
+
sum += counter
|
272
|
+
results << counter
|
273
|
+
end
|
274
|
+
rescue
|
275
|
+
puts "Error: " + $!
|
276
|
+
end
|
150
277
|
end
|
278
|
+
|
279
|
+
# Add the totals to the results
|
280
|
+
results << sum
|
281
|
+
|
282
|
+
# Write the metrics to the required files in the appropriate formats
|
283
|
+
writers.each { |writer| writer.write(results) }
|
284
|
+
|
285
|
+
return results
|
151
286
|
end
|
152
287
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
end
|
288
|
+
module_function :countloc
|
289
|
+
|
290
|
+
end # module
|
157
291
|
|
158
292
|
#
|
159
293
|
# When run as a standalone script ...
|
160
294
|
#
|
161
295
|
if $0 == __FILE__:
|
162
|
-
|
296
|
+
|
163
297
|
require 'ostruct'
|
164
|
-
|
298
|
+
|
165
299
|
class CmdLineOptParser
|
166
|
-
|
300
|
+
|
167
301
|
def self.usage
|
168
|
-
"Usage: #{File.basename($0)} [options] <file>"
|
302
|
+
"Usage: #{File.basename($0)} [-h|--help] [options] <file> ... <file>"
|
169
303
|
end
|
170
304
|
|
171
305
|
#
|
@@ -176,37 +310,57 @@ if $0 == __FILE__:
|
|
176
310
|
# Setup the defaults here
|
177
311
|
options = OpenStruct.new
|
178
312
|
options.recurse = false
|
313
|
+
options.csv = false
|
314
|
+
options.csvFilename = ""
|
315
|
+
options.html = false
|
316
|
+
options.htmlFilename = ""
|
179
317
|
|
180
|
-
|
181
|
-
|
318
|
+
begin
|
319
|
+
OptionParser.new do |opts|
|
320
|
+
opts.banner = usage
|
182
321
|
|
183
|
-
|
184
|
-
|
185
|
-
|
322
|
+
opts.on('-r', '--recurse', 'Recurse into subdirectories') do
|
323
|
+
options.recurse = true
|
324
|
+
end
|
186
325
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
326
|
+
opts.on('-v', '--version', 'Display version number') do
|
327
|
+
puts "#{File.basename($0)}, version: #{VERSION}"
|
328
|
+
exit
|
329
|
+
end
|
330
|
+
|
331
|
+
opts.on('--csv csvFilename', 'Generate csv file') do |csvFilename|
|
332
|
+
options.csvFilename = csvFilename
|
333
|
+
options.csv = true
|
334
|
+
end
|
335
|
+
|
336
|
+
opts.on('--html htmlFilename', 'Generate html file') do |htmlFilename|
|
337
|
+
options.htmlFilename = htmlFilename
|
338
|
+
options.html = true
|
339
|
+
end
|
340
|
+
|
341
|
+
opts.on_tail('-h', '--help', 'Display this help and exit') do
|
342
|
+
puts opts
|
343
|
+
exit
|
344
|
+
end
|
345
|
+
|
346
|
+
end.parse!(args)
|
347
|
+
|
348
|
+
rescue
|
349
|
+
puts "Error: " + $!
|
350
|
+
exit
|
351
|
+
end
|
191
352
|
|
192
|
-
opts.on_tail('-h', '--help', 'display this help and exit') do
|
193
|
-
puts opts
|
194
|
-
exit
|
195
|
-
end
|
196
|
-
|
197
|
-
end.parse!(args)
|
198
|
-
|
199
353
|
options
|
200
354
|
end # parse()
|
201
355
|
end # class CmdLineOptParser
|
202
|
-
|
356
|
+
|
203
357
|
options = CmdLineOptParser.parse(ARGV)
|
204
|
-
|
358
|
+
|
205
359
|
if ARGV.length < 1
|
206
360
|
puts CmdLineOptParser.usage
|
207
361
|
exit
|
208
362
|
end
|
209
363
|
|
210
|
-
countloc(ARGV, options)
|
364
|
+
CountLOC.countloc(ARGV, options)
|
211
365
|
|
212
366
|
end
|