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 +42 -7
- data/lib/countloc.rb +179 -68
- data/test/tc_ruby.rb +94 -14
- metadata +4 -6
- data/test/ruby_multiline_comments.rb +0 -7
- data/test/ruby_single_line_comments.rb +0 -23
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
|
-
|
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.
|
data/lib/countloc.rb
CHANGED
@@ -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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
90
|
-
|
91
|
-
opts.banner = usage
|
92
|
-
end.parse!
|
180
|
+
OptionParser.new do |opts|
|
181
|
+
opts.banner = usage
|
93
182
|
|
94
|
-
|
95
|
-
|
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
|
-
|
100
|
-
|
210
|
+
countloc(ARGV, options)
|
211
|
+
|
101
212
|
end
|
data/test/tc_ruby.rb
CHANGED
@@ -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
|
11
|
-
|
12
|
-
assert_equal(
|
13
|
-
assert_equal(
|
14
|
-
assert_equal(
|
15
|
-
assert_equal(
|
16
|
-
end
|
17
|
-
|
18
|
-
def
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
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:
|
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,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
|