countloc 0.1.0 → 0.2.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/{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
|