countloc 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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