countloc 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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.
@@ -23,149 +23,283 @@
23
23
 
24
24
 
25
25
  require 'optparse'
26
+ require 'time'
26
27
 
27
- COUNTLOC_VERSION = '0.1.0'
28
+ #
29
+ # CountLOC module that encapsulates the methods and classes used to gather
30
+ # code metrics.
31
+ #
32
+ module CountLOC
28
33
 
29
- # Class that gathers the metrics.
30
- #
31
- # This class design & implementation is based heavily on Stefan Lang's
32
- # ScriptLines class from the "Ruby Cookbook" - Recipe 19.5 "Gathering
33
- # Statistics About Your Code".
34
- class LineCounter
35
- attr_reader :name
36
- attr_accessor :code, :comments, :blank, :lines
37
-
38
- SINGLE_LINE_FULL_PATTERN = /^\s*#/
39
- SINGLE_LINE_MIXED_PATTERN = /#/
40
- MULTI_LINE_BEGIN_PATTERN = /=begin(\s|$)/
41
- MULTI_LINE_END_PATTERN = /=end(\s|$)/
42
- BLANK_LINE_PATTERN = /^\s*$/
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
- LINE_FORMAT = '%8s %8s %8s %8s %s'
145
+ end
45
146
 
46
147
  #
47
- # Generate a string that contains the column headers for the metrics
48
- # printed with to_s
148
+ # Container class to store results from each file.
49
149
  #
50
- def self.headline
51
- sprintf LINE_FORMAT, "LOC", "COMMENTS", "BLANK", "LINES", "FILE"
52
- end
53
-
54
- def initialize(name)
55
- @name = name
56
- @code = 0
57
- @comments = 0
58
- @blank = 0
59
- @lines = 0
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
- # Get a new LineCounter instance whose counters hold the sum of self
103
- # and other.
190
+ # Class that writes to the Console
104
191
  #
105
- def +(other)
106
- sum = self.dup
107
- sum.code += other.code
108
- sum.comments += other.comments
109
- sum.blank += other.blank
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
- # Get a formatted string containing all counter numbers and the name of
116
- # this instance.
200
+ # Class that writes to a csv file
117
201
  #
118
- def to_s
119
- sprintf LINE_FORMAT, @code, @comments, @blank, @lines, @name
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
- # Generates LOC metrics for the specified files and sends the results to the console.
126
- #
127
- def countloc(files, options = nil)
128
-
129
- # Sum will keep the running total
130
- sum = LineCounter.new("TOTAL")
131
-
132
- # Print a banner showing the column headers
133
- puts LineCounter.headline
134
-
135
- # Expand directories into the appropriate file lists
136
- dirs = files.select { |filename| File.directory?(filename) }
137
- if dirs.size > 0
138
- recursePattern = ("**" if options.recurse) || ""
139
- files -= dirs
140
- files += dirs.collect { |dirname| Dir.glob(File.join(dirname, recursePattern, "*.rb"))}.flatten
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
- # Generate metrics for each file
144
- files.each do |filename|
145
- File.open(filename) do |file|
146
- counter = LineCounter.new(filename)
147
- counter.read(file)
148
- sum += counter
149
- puts counter
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
- # Print the total stats
154
- puts sum
155
- return sum
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
- OptionParser.new do |opts|
181
- opts.banner = usage
318
+ begin
319
+ OptionParser.new do |opts|
320
+ opts.banner = usage
182
321
 
183
- opts.on('-r', '--recurse', 'Recurse into subdirectories') do |r|
184
- options.recurse = true
185
- end
322
+ opts.on('-r', '--recurse', 'Recurse into subdirectories') do
323
+ options.recurse = true
324
+ end
186
325
 
187
- opts.on('-v', '--version', 'Display version number') do
188
- puts "#{File.basename($0)}, version: #{COUNTLOC_VERSION}"
189
- exit
190
- end
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