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