ohcount option -i or --individual added
[ohcount] / bin / ohcount
1 #!/usr/bin/env ruby
2 begin
3         # First assume we are installed as a gem
4         require 'rubygems'
5         require 'ohcount'
6 rescue LoadError
7         # Failing that, try to load our files from the source tree
8         $: << "#{File.dirname(__FILE__)}/../lib"
9         require 'ohcount'
10 end
11
12 class OhcountCommandLine
13         attr_accessor :paths
14
15         def initialize(args=[])
16                 args = args.clone # Because shift is destructive
17                 set_option(args.shift) while args.first =~ /^-/
18                 self.paths = args
19         end
20
21         def paths
22                 @paths || []
23         end
24
25         def paths=(p)
26                 @paths=p
27                 @files = nil # If we change the search paths, clear the cache filenames
28         end
29
30         def files
31                 return @files if @files
32
33                 @files = `find #{(self.paths.any? ? self.paths : ['.']).collect{ |s| "'#{s}'" }.join(' ')} \! -path "*/.*" -type f -print`.split("\n")
34                 exit 1 unless @files.any?
35                 @files
36         end
37
38         def annotate
39                 files.each do |file|
40                         sfc = Ohcount::SimpleFileContext.new(file, files)
41                         polyglot = Ohcount::Detector.detect(sfc)
42                         if polyglot
43                                 Ohcount::parse(sfc.contents, polyglot) do |language, semantic, line|
44                                         puts "#{language}\t#{semantic}\t#{line}"
45                                 end
46                         end
47                 end
48         end
49
50         # Find all source code files
51         def detect
52                 files.each do |file|
53                         sfc = Ohcount::SimpleFileContext.new(file, files)
54                         polyglot = Ohcount::Detector.detect(sfc)
55                         puts "#{polyglot}\t#{file}" if polyglot
56                 end
57         end
58
59         def help
60                 puts <<HELP
61 Usage: ohcount [option] [paths...]
62
63 Ohloh source code line counter command line tool.
64    http://www.ohloh.net/
65
66 [option] can be one of the following:
67    -a, --annotate
68    -d, --detect
69    -h, --help
70    -s, --summary
71
72 -a, --annotate                  Show annotated source code
73
74    The contents of all source code files found within the given
75    paths will be emitted to stdout. Each line will be prefixed with
76    a tab-delimited language name and semantic categorization (code,
77    comment, or blank).
78
79 -d, --detect                    Find source code files
80
81    Recursively find all source code files within the given paths.
82    For each source code file found, the file name will be emitted to
83    stdout prefixed with a tab-delimited language name.
84
85 -h, --help                      Display this message
86
87 -i, --individual                Count lines of code per file
88
89    Count lines in all source code files within the given paths, and
90    emit a report of the lines of code, comments, and blanks in each
91    language per file.
92
93 -s, --summary                   Count lines of code (default)
94
95    Count lines in all source code files within the given paths, and
96    emit a report of the total number of lines of code, comments,
97    and blanks in each language. This is the default action.
98
99 [paths] can refer to any number of individual files or directories.
100    Directories will be probed recursively. If no path is given,
101    the current directory will be used.
102
103 HELP
104         end
105
106         def individual
107                 STDOUT.write "Examining #{files.size} file(s)"
108
109                 puts
110                 puts "Ohloh Line Count".center(76)
111                 puts "File                                             Language             Code    Comment  Comment %      Blank      Total"
112                 puts "-----------------------------------------------  --------------  ---------  ---------  ---------  ---------  ---------"
113
114                 files.each do |file|
115                         counts = {}
116                         languages_found=[]
117                         sfc = Ohcount::SimpleFileContext.new(file, files)
118                         polyglot = Ohcount::Detector.detect(sfc)
119                         if polyglot
120                                 Ohcount::parse(sfc.contents, polyglot) do |language_name, semantic, line|
121                                         counts[language_name] ||={:code => 0, :comment => 0, :blank => 0}
122                                         counts[language_name][semantic] += 1
123                                         languages_found << language_name unless languages_found.include?(language_name)
124                                 end
125                         end
126
127                         counts.keys.sort{ |a,b| counts[b][:code] <=> counts[a][:code] }.each do |key|
128                                 write_individual_row(file, key, counts[key][:code],counts[key][:comment],counts[key][:blank])
129                         end
130                 end
131         end
132
133         def write_individual_row(file, name, code, comment, blank)
134                 printf("%-48s", file)
135                 printf(" %-14s",name)
136                 printf(" %10d",code)
137                 printf(" %10d",comment)
138                 if comment+code > 0
139                         printf(" %9.1f%%", comment.to_f / (comment+code).to_f * 100.0)
140                 else
141                         printf("           ")
142                 end
143                 printf(" %10d",blank)
144                 printf(" %10d\n",code+comment+blank)
145         end
146
147
148         def summary
149                 STDOUT.write "Examining #{files.size} file(s)"
150
151                 counts = {}
152                 any_language_file_count = 0
153                 progress = 0
154
155                 files.each do |file|
156                         languages_found = []
157                         sfc = Ohcount::SimpleFileContext.new(file, files)
158                         polyglot = Ohcount::Detector.detect(sfc)
159                         if polyglot
160                                 Ohcount::parse(sfc.contents, polyglot) do |language_name, semantic, line|
161                                         counts[language_name] ||= {:code => 0, :comment => 0, :blank => 0}
162                                         counts[language_name][semantic] += 1
163                                         languages_found << language_name unless languages_found.include?(language_name)
164                                 end
165                         end
166
167                         # Keep a running total of the number of files that include a specific language
168                         languages_found.each { |l| counts[l][:files] = (counts[l][:files] || 0) + 1 }
169
170                         # Keep a running total of the number of files that include any language
171                         any_language_file_count += 1 if languages_found.any?
172
173                         progress += 1
174                         if (progress % 100 == 0)
175                                 STDOUT.write('.')
176                                 STDOUT.flush
177                         end
178                 end
179
180                 puts
181                 puts "Ohloh Line Count Summary".center(76)
182                 puts
183
184                 puts "Language        Files       Code    Comment  Comment %      Blank      Total"
185                 puts "--------------  -----  ---------  ---------  ---------  ---------  ---------"
186
187                 counts.keys.sort{ |a,b| counts[b][:code] <=> counts[a][:code] }.each do |key|
188                         write_summary_row(key, counts[key][:files], counts[key][:code], counts[key][:comment], counts[key][:blank])
189                 end
190
191                 puts "--------------  -----  ---------  ---------  ---------  ---------  ---------"
192                 write_summary_row('Total', any_language_file_count,
193                                                                                         counts.values.inject(0) { |sum, v| sum + v[:code] },
194                                                                                         counts.values.inject(0) { |sum, v| sum + v[:comment] },
195                                                                                         counts.values.inject(0) { |sum, v| sum + v[:blank] })
196         end
197
198         def write_summary_row(name, file_count, code, comment, blank)
199                 printf("%-14s", name)
200                 printf(" %6d", file_count)
201                 printf(" %10d", code)
202                 printf(" %10d", comment)
203                 if comment+code > 0
204                         printf(" %9.1f%%", comment.to_f / (comment+code).to_f * 100.0)
205                 end
206                 printf(" %10d", blank)
207                 printf(" %10d\n", code+comment+blank)
208         end
209
210         def subcommand=(s)
211                 if @subcommand
212                         STDERR.puts "Error: Multiple commands specified."
213                         exit 1
214                 else
215                         @subcommand=s
216                 end
217         end
218
219         def subcommand
220                 @subcommand
221         end
222
223         def set_option(option)
224                 case option
225                 when '-s', '--summary'
226                         self.subcommand = :summary
227                 when '-d', '--detect'
228                         self.subcommand = :detect
229                 when '-a', '--annotate'
230                         self.subcommand = :annotate
231                 when '-i', '--individual'
232                         self.subcommand = :individual
233                 when '-?', '-h', '--help'
234                         self.subcommand = :help
235                 else
236                         STDERR.puts "Type 'ohcount -?' for usage."
237                         exit 1
238                 end
239         end
240
241         def run!
242                 self.subcommand ||= :summary
243                 if self.respond_to?(self.subcommand)
244                         self.send(self.subcommand)
245                 else
246                         STDERR.puts "Type 'ohcount -?' for usage."
247                         exit 1
248                 end
249         end
250 end
251
252 OhcountCommandLine.new(ARGV).run!