countloc 0.0.1 → 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/README.txt CHANGED
@@ -1,4 +1,4 @@
1
- Introduction
1
+ = Introduction
2
2
  ------------
3
3
 
4
4
  Contents:
@@ -6,6 +6,7 @@ Contents:
6
6
  1. Overview
7
7
  2. Installation
8
8
  3. Usage
9
+ 4. Release Notes
9
10
 
10
11
 
11
12
  1. Overview
@@ -17,13 +18,15 @@ Initial releases will support counting lines of code in Ruby source code.
17
18
  Subsequent releases will add support for other programming languages such as
18
19
  Python, C, C++, C#, Java, Perl, etc. ...
19
20
 
20
-
21
21
  2. Installation
22
22
  ---------------
23
- CountLOC is packaged as a Ruby gem and as a .zip file.
23
+ CountLOC is packaged as a Ruby gem and as a .zip file. Within each archive, the
24
+ countloc.rb script is located in the lib directory.
25
+
26
+ The latest release can be downloaded from: http://rubyforge.org/frs/?group_id=7555&release_id=29931
24
27
 
25
28
  Gem:
26
- ruby gem install countloc.gem
29
+ gem install countloc.gem
27
30
 
28
31
  zip:
29
32
  unzip countloc.zip
@@ -32,8 +35,40 @@ zip:
32
35
  3. Usage
33
36
  --------
34
37
  For full listing of options and usage:
35
- countloc --help
38
+ countloc.rb --help
36
39
 
37
40
  To get the LOC metrics for a single Ruby file:
38
- countloc some_file.rb
39
-
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 countloc.rb .
49
+
50
+ To get the LOC metrics for all Ruby files in a directory tree with recursion
51
+ into subdirectories:
52
+ countloc.rb -r .
53
+
54
+
55
+ 4. Release Notes
56
+ ----------------
57
+ Release 0.1.0:
58
+ * Features
59
+ ** Added support for processing multiple files at once.
60
+ ** Added support for processing all .rb files in a directory.
61
+ ** Added support for recursing into directory trees.
62
+ ** Improved formatting of output.
63
+
64
+ * Bugfixes:
65
+ ** #23380 - Not suppressing path information in the program name in the usage string.
66
+ ** #23379 - Incorrectly counting a # inside a single quoted string as a comment.
67
+
68
+ Release 0.0.1:
69
+ * Features
70
+ ** Initial release.
71
+ ** Supports generating LOC metrics for Ruby source code for a single
72
+ file at a time.
73
+ ** Handles multi-line and single line comments.
74
+ ** Handles mixed lines of both code and comments.
@@ -1,3 +1,5 @@
1
+ #!/usr/bin/ruby
2
+ #
1
3
  # = countloc.rb - Ruby line counter.
2
4
  #
3
5
  # Copyright (C) 2008 Stephen Doyle
@@ -9,93 +11,202 @@
9
11
  # * Single line comments - starting from a # until the end of the line.
10
12
  # * Multi-line comments - between "=begin" and "=end" tags.
11
13
  #
14
+ # == Download
15
+ # The latest countloc release can be downloaded from RubyForge:
16
+ # http://rubyforge.org/frs/?group_id=7555&release_id=29931
17
+ #
12
18
  # == Example
13
- # countloc.rb --help
14
- # countloc.rb some_file.rb
19
+ # * countloc.rb --help
20
+ # * countloc.rb some_file.rb
21
+ # * countloc.rb -r .
15
22
  #
16
23
 
24
+
17
25
  require 'optparse'
18
26
 
19
- class CountLocPatterns
20
- attr_reader :single_line_full # Entire line is a single line comment
21
- attr_reader :single_line_mixed # Mixed code and comment on same line
22
- attr_reader :multi_line_begin # Beginning of a multi-line comment
23
- attr_reader :multi_line_end # End of a multi-line comment
27
+ COUNTLOC_VERSION = '0.1.0'
28
+
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
24
37
 
25
- def initialize
26
- @single_line_full = /^\s*#/
27
- @single_line_mixed = /#/
28
- @multi_line_begin = /=begin/
29
- @multi_line_end = /=end/
30
- end
31
- end
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*$/
32
43
 
33
- # Get the lines of code (LOC) metrics for the specified filename(s).
34
- # Returns: Metrics in the form of a hash of name-value pairs.
35
- def countloc(filename)
36
- stats = { 'comments'=>0, 'code'=>0, 'blank'=>0, 'total'=>0 }
37
- patterns = CountLocPatterns.new
38
- isMultilineOpen = false
44
+ LINE_FORMAT = '%8s %8s %8s %8s %s'
45
+
46
+ #
47
+ # Generate a string that contains the column headers for the metrics
48
+ # printed with to_s
49
+ #
50
+ def self.headline
51
+ sprintf LINE_FORMAT, "LOC", "COMMENTS", "BLANK", "LINES", "FILE"
52
+ end
39
53
 
40
- srcFile = File.new(filename, 'r').each do |line|
41
- stats['total'] += 1
42
-
43
- if isMultilineOpen
44
- if line =~ patterns.multi_line_end
45
- isMultilineOpen = false
46
- end
47
- stats['comments'] += 1
48
- next
49
- end
50
-
51
- if line =~ /^\s*$/ # Blank line
52
- stats['blank'] += 1
53
- next
54
- end
55
-
56
- if line =~ patterns.multi_line_begin
57
- isMultilineOpen = true
58
- stats['comments'] += 1
59
- next
60
- end
61
-
62
- if line =~ patterns.single_line_full
63
- stats['comments'] += 1
64
- next
65
- end
54
+ def initialize(name)
55
+ @name = name
56
+ @code = 0
57
+ @comments = 0
58
+ @blank = 0
59
+ @lines = 0
60
+ end
66
61
 
67
- # Process the line to avoid matching comment characters within quoted
68
- # strings or regular expressions.
69
- line.gsub!(/\".*?\"/, "X") # Quoted string
70
- line.gsub!(/\/.*?\//, "X") # Regular expression
71
-
72
- if line =~ patterns.single_line_mixed
73
- stats['comments'] += 1
74
- stats['code'] += 1
75
- else
76
- stats['code'] += 1
77
- end
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
+ #
102
+ # Get a new LineCounter instance whose counters hold the sum of self
103
+ # and other.
104
+ #
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
78
112
  end
79
113
 
80
- stats['code:comment'] = stats['code'].to_f / stats['comments']
114
+ #
115
+ # Get a formatted string containing all counter numbers and the name of
116
+ # this instance.
117
+ #
118
+ def to_s
119
+ sprintf LINE_FORMAT, @code, @comments, @blank, @lines, @name
120
+ end
81
121
 
82
- return stats
83
122
  end
123
+
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
141
+ end
142
+
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
150
+ end
151
+ end
84
152
 
153
+ # Print the total stats
154
+ puts sum
155
+ return sum
156
+ end
157
+
158
+ #
85
159
  # When run as a standalone script ...
160
+ #
86
161
  if $0 == __FILE__:
87
- usage = "Usage: #{$0} [-h --help] <file>"
162
+
163
+ require 'ostruct'
164
+
165
+ class CmdLineOptParser
166
+
167
+ def self.usage
168
+ "Usage: #{File.basename($0)} [options] <file>"
169
+ end
170
+
171
+ #
172
+ # Return a structure describing the options
173
+ #
174
+ def self.parse(args)
175
+ # The options set on the command line will be collected in "options"
176
+ # Setup the defaults here
177
+ options = OpenStruct.new
178
+ options.recurse = false
88
179
 
89
- options = {}
90
- OptionParser.new do |opts|
91
- opts.banner = usage
92
- end.parse!
180
+ OptionParser.new do |opts|
181
+ opts.banner = usage
93
182
 
94
- if ARGV.length != 1
95
- puts usage
183
+ opts.on('-r', '--recurse', 'Recurse into subdirectories') do |r|
184
+ options.recurse = true
185
+ end
186
+
187
+ opts.on('-v', '--version', 'Display version number') do
188
+ puts "#{File.basename($0)}, version: #{COUNTLOC_VERSION}"
189
+ exit
190
+ end
191
+
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
+ options
200
+ end # parse()
201
+ end # class CmdLineOptParser
202
+
203
+ options = CmdLineOptParser.parse(ARGV)
204
+
205
+ if ARGV.length < 1
206
+ puts CmdLineOptParser.usage
96
207
  exit
97
208
  end
98
209
 
99
- stats = countloc(ARGV[0])
100
- stats.each { |k,v| puts "#{k} = #{v}" }
210
+ countloc(ARGV, options)
211
+
101
212
  end
@@ -6,21 +6,101 @@ require 'test/unit'
6
6
  require 'countloc'
7
7
 
8
8
  class RubyTest < Test::Unit::TestCase
9
+
10
+ def setup
11
+ @counter = LineCounter.new('Test')
12
+ end
13
+
14
+ def test_ruby_single_line_comment
15
+ @counter.read("# This is a comment")
16
+ assert_equal(0, @counter.code)
17
+ assert_equal(1, @counter.comments)
18
+ assert_equal(0, @counter.blank)
19
+ assert_equal(1, @counter.lines)
20
+ end
21
+
22
+ def test_ruby_single_line_comment_with_whitespace
23
+ @counter.read(" # This is a comment")
24
+ assert_equal(0, @counter.code)
25
+ assert_equal(1, @counter.comments)
26
+ assert_equal(0, @counter.blank)
27
+ assert_equal(1, @counter.lines)
28
+ end
29
+
30
+ def test_ruby_multi_line_comment
31
+ @counter.read("=begin\n# This is a comment\n=end")
32
+ assert_equal(0, @counter.code)
33
+ assert_equal(3, @counter.comments)
34
+ assert_equal(0, @counter.blank)
35
+ assert_equal(3, @counter.lines)
36
+ end
37
+
38
+ def test_ruby_multi_line_comment_with_blank_lines
39
+ @counter.read("=begin\n# This is a comment\n\n=end")
40
+ assert_equal(0, @counter.code)
41
+ assert_equal(3, @counter.comments)
42
+ assert_equal(1, @counter.blank)
43
+ assert_equal(4, @counter.lines)
44
+ end
45
+
46
+ def test_ruby_mixed
47
+ @counter.read("puts 'hello' # This is a mixed comment")
48
+ assert_equal(1, @counter.code)
49
+ assert_equal(1, @counter.comments)
50
+ assert_equal(0, @counter.blank)
51
+ assert_equal(1, @counter.lines)
52
+ end
53
+
54
+ def test_ruby_comment_char_in_double_quote_string
55
+ @counter.read('puts "hello #"')
56
+ assert_equal(1, @counter.code)
57
+ assert_equal(0, @counter.comments)
58
+ assert_equal(0, @counter.blank)
59
+ assert_equal(1, @counter.lines)
60
+ end
61
+
62
+ def test_ruby_comment_char_in_single_quote_string
63
+ @counter.read("puts 'hello #'")
64
+ assert_equal(1, @counter.code)
65
+ assert_equal(0, @counter.comments)
66
+ assert_equal(0, @counter.blank)
67
+ assert_equal(1, @counter.lines)
68
+ end
69
+
70
+ def test_ruby_comment_char_in_regexp
71
+ @counter.read("puts /#/")
72
+ assert_equal(1, @counter.code)
73
+ assert_equal(0, @counter.comments)
74
+ assert_equal(0, @counter.blank)
75
+ assert_equal(1, @counter.lines)
76
+ end
77
+
78
+ def test_ruby_blank_lines_with_newline
79
+ @counter.read("\n")
80
+ assert_equal(0, @counter.code)
81
+ assert_equal(0, @counter.comments)
82
+ assert_equal(1, @counter.blank)
83
+ assert_equal(1, @counter.lines)
84
+ end
9
85
 
10
- def test_ruby_single_line_comments
11
- stats = countloc('ruby_single_line_comments.rb')
12
- assert_equal(14, stats['code'])
13
- assert_equal(6, stats['comments'])
14
- assert_equal(5, stats['blank'])
15
- assert_equal(23, stats['total'])
16
- end
17
-
18
- def test_ruby_multi_line_comments
19
- stats = countloc('ruby_multiline_comments.rb')
20
- assert_equal(2, stats['code'])
21
- assert_equal(4, stats['comments'])
22
- assert_equal(1, stats['blank'])
23
- assert_equal(7, stats['total'])
86
+ def test_ruby_blank_lines_with_whitespace
87
+ @counter.read(" \t")
88
+ assert_equal(0, @counter.code)
89
+ assert_equal(0, @counter.comments)
90
+ assert_equal(1, @counter.blank)
91
+ assert_equal(1, @counter.lines)
92
+ end
93
+
94
+ def test_ruby_mixed_code_and_comments
95
+ @counter.read("# This is a comment\n" +
96
+ "1.upto(5).each {|x| puts x}\n" +
97
+ "=begin\n" +
98
+ " multiline comment\n" +
99
+ "=end")
100
+ assert_equal(1, @counter.code)
101
+ assert_equal(4, @counter.comments)
102
+ assert_equal(0, @counter.blank)
103
+ assert_equal(5, @counter.lines)
24
104
  end
25
105
 
26
106
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: countloc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Doyle
@@ -9,12 +9,12 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-12-30 00:00:00 +00:00
12
+ date: 2009-01-01 00:00:00 +00:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
16
16
  description: LOC metrics generation script implementation in Ruby.
17
- email:
17
+ email: support@stephendoyle.net
18
18
  executables: []
19
19
 
20
20
  extensions: []
@@ -25,8 +25,6 @@ files:
25
25
  - README.txt
26
26
  - LICENSE.txt
27
27
  - lib/countloc.rb
28
- - test/ruby_multiline_comments.rb
29
- - test/ruby_single_line_comments.rb
30
28
  - test/tc_ruby.rb
31
29
  - test/ts_countloc.rb
32
30
  has_rdoc: true
@@ -56,6 +54,6 @@ rubyforge_project: countloc
56
54
  rubygems_version: 1.0.1
57
55
  signing_key:
58
56
  specification_version: 2
59
- summary: Ruby line counter - countLOC.
57
+ summary: Ruby line counter - countLOC
60
58
  test_files:
61
59
  - test/ts_countloc.rb
@@ -1,7 +0,0 @@
1
- =begin
2
- This is a ruby file that uses multi-line comments.
3
- It is used for testing countloc
4
- =end
5
-
6
- puts "Hello World"
7
- puts "Blah, blah, blah ..."
@@ -1,23 +0,0 @@
1
- # This is a sample piece of ruby code to test countloc with single
2
- # line comments.
3
- puts "Hello World"
4
-
5
- # And some more code ...
6
- 1.upto(10) { |x|puts x}
7
-
8
- # And yet more ...
9
- ('a'..'z').each do |alpha|
10
- puts alpha # This is an example of mixed code and comments.
11
- end
12
-
13
- if "abcdefg".include? "#"
14
- puts "We should never get here!"
15
- end
16
-
17
- if "abcdefg" =~ /#/
18
- puts "We should never get here!"
19
- end
20
-
21
- if "abcdefg" =~ /#/ # This is mixed also!
22
- puts "We should never get here!"
23
- end