[FIX] license listing failed due to missing namespace
[ohcount] / bin / ohcount
1 #!/usr/bin/env ruby
2 begin
3         # First, try to load our files from the source tree
4         $: << "#{File.dirname(__FILE__)}/../lib"
5         require 'ohcount'
6 rescue LoadError
7         # Failing that, try to load from a gem
8         require 'rubygems'
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                 assign_paths_and_files(args)
19         end
20
21         def assign_paths_and_files(args)
22                 @files = []
23                 @paths = []
24                 args.each do |file_or_path|
25                         if File.directory?(file_or_path)
26                                 @paths << file_or_path
27                         else
28                                 @files << file_or_path
29                         end
30                 end
31                 @paths = ['.'] if @files.empty? && @paths.empty?
32         end
33
34         def source_file_list
35                 @source_file_list ||= Ohcount::SourceFileList.new(:paths => @paths, :files => @files)
36         end
37
38         def annotate
39                 source_file_list.each_source_file do |s|
40                         s.parse do |language, semantic, line|
41                                 puts "#{language}\t#{semantic}\t#{line}"
42                         end
43                 end
44         end
45
46         # Find all source code files
47         def detect
48                 source_file_list.each_source_file do |s|
49                         puts "#{s.polyglot}\t#{s.filename}"
50                 end
51         end
52
53   # Licenses
54   def licenses
55                 source_file_list.each_source_file do |s|
56       next unless s.licenses.any?
57                         symbols = s.licenses.collect { |l| Ohcount::LicenseSniffer::LicenseMap.instance.map[l].symbol }.join(",")
58                         puts "#{symbols}\t#{s.filename}"
59                 end
60   end
61
62         def analyze_with_progress(what)
63                 count = 0
64                 source_file_list.analyze(what) do |s|
65                         if count % 100 == 0
66                                 STDOUT.write('.')
67                                 STDOUT.flush
68                         end
69                         count+=1
70                 end
71                 puts "\n"
72         end
73
74         def write_gestalt
75                 platforms = source_file_list.gestalt_facts.platforms
76                 platforms = platforms.any? ? platforms.collect { |p| p.to_s.split('::').last }.join(", ") : 'None'
77                 puts "Platforms detected: #{ platforms }"
78
79                 tools = source_file_list.gestalt_facts.tools
80                 tools = tools.any? ? tools.collect { |p| p.to_s.split('::').last }.join(", ") : 'None'
81                 puts "Tools detected: #{ tools }"
82         end
83
84   # Gestalt
85         def gestalt
86                 analyze_with_progress(:gestalt)
87                 write_gestalt
88         end
89
90         def raw_entities
91                 source_file_list.each_source_file do |s|
92                         s.raw_entities do |language, entity, s, e|
93                                 puts "#{language}\t#{entity}\t#{s}\t#{e}"
94                         end
95                 end
96         end
97
98         def help
99                 puts <<HELP
100 Usage: ohcount [option] [paths...]
101
102 Ohloh source code line counter command line tool.
103    http://www.ohloh.net/
104
105 [option] can be one of the following:
106    -a, --annotate
107    -d, --detect
108          -re
109    -h, --help
110    -s, --summary
111
112 -a, --annotate                  Show annotated source code
113
114    The contents of all source code files found within the given
115    paths will be emitted to stdout. Each line will be prefixed with
116    a tab-delimited language name and semantic categorization (code,
117    comment, or blank).
118
119 -d, --detect                    Find source code files
120
121    Recursively find all source code files within the given paths.
122    For each source code file found, the file name will be emitted to
123    stdout prefixed with a tab-delimited language name.
124
125 -h, --help                      Display this message
126
127 -g, --gestalt                   Project Properties
128
129    Inspects project contents to determine what platform(s) the project
130    runs on, as well as any detected tools/IDEs used to develop it.
131
132 -i, --individual                Count lines of code per file
133
134    Count lines in all source code files within the given paths, and
135    emit a report of the lines of code, comments, and blanks in each
136    language per file.
137
138 -l, --license
139
140    Displays detected licensing information contained in each source
141    code file.
142
143 -re
144
145    Prints raw entity information to the screen (mainly for debugging).
146
147 -s, --summary                   Count lines of code (default)
148
149    Count lines in all source code files within the given paths, and
150    emit a report of the total number of lines of code, comments,
151    and blanks in each language. This is the default action.
152
153 [paths] can refer to any number of individual files or directories.
154    Directories will be probed recursively. If no path is given,
155    the current directory will be used.
156
157 HELP
158         end
159
160         def individual
161                 STDOUT.write "Examining #{source_file_list.size} file(s)"
162
163                 puts
164                 puts "Ohloh Line Count".center(76)
165                 puts "Language               Code    Comment  Comment %      Blank      Total  File"
166                 puts "----------------  ---------  ---------  ---------  ---------  ---------  -----------------------------------------------"
167
168                 source_file_list.analyze(:language) do |s|
169                         s.language_breakdowns.sort {|l1,l2| l2.name <=> l1.name}.each do |lb|
170                                 write_individual_row(s.filename, lb.name, lb.code_count, lb.comment_count, lb.blanks)
171                         end
172                 end
173         end
174
175         def write_individual_row(file, name, code, comment, blank)
176                 printf("%-16s",name)
177                 printf(" %10d",code)
178                 printf(" %10d",comment)
179                 if comment+code > 0
180                         printf(" %9.1f%%", comment.to_f / (comment+code).to_f * 100.0)
181                 else
182                         printf("           ")
183                 end
184                 printf(" %10d",blank)
185                 printf(" %10d",code+comment+blank)
186                 printf("  %s\n", file)
187         end
188
189         def memoryleak
190                 puts "Parsing one file repeatedly. Watch your RAM vanish...."
191                 s = Ohcount::SourceCode.new(files.first, :filenames => files)
192                 while true
193                         s.parse {}
194                 end
195         end
196
197         def summary
198                 STDOUT.write "Examining #{source_file_list.size} file(s)"
199
200                 analyze_with_progress(:*)
201
202                 puts
203                 puts "Ohloh Line Count Summary".center(76)
204                 puts
205
206                 puts "Language          Files       Code    Comment  Comment %      Blank      Total"
207                 puts "----------------  -----  ---------  ---------  ---------  ---------  ---------"
208
209                 source_file_list.loc_list.locs.sort { |a,b| b.code <=> a.code}.each do |loc|
210                         write_summary_row(loc.language, loc.filecount, loc.code, loc.comments, loc.blanks)
211                 end
212
213                 puts "----------------  -----  ---------  ---------  ---------  ---------  ---------"
214                 write_summary_row('Total',
215                                                                                         source_file_list.loc_list.filecount,
216                                                                                         source_file_list.loc_list.code,
217                                                                                         source_file_list.loc_list.comments,
218                                                                                         source_file_list.loc_list.blanks)
219
220
221                 puts
222                 write_gestalt
223         end
224
225         def write_summary_row(name, file_count, code, comment, blank)
226                 printf("%-16s", name)
227                 printf(" %6d", file_count)
228                 printf(" %10d", code)
229                 printf(" %10d", comment)
230                 if comment+code > 0
231                         printf(" %9.1f%%", comment.to_f / (comment+code).to_f * 100.0)
232                 else
233                         printf("       0.0%");
234                 end
235                 printf(" %10d", blank)
236                 printf(" %10d\n", code+comment+blank)
237         end
238
239         def subcommand=(s)
240                 if @subcommand
241                         STDERR.puts "Error: Multiple commands specified."
242                         exit 1
243                 else
244                         @subcommand=s
245                 end
246         end
247
248         def subcommand
249                 @subcommand
250         end
251
252         def set_option(option)
253                 case option
254                 when '-s', '--summary'
255                         self.subcommand = :summary
256                 when '-d', '--detect'
257                         self.subcommand = :detect
258                 when '-a', '--annotate'
259                         self.subcommand = :annotate
260                 when '-g', '--gestalt'
261                         self.subcommand = :gestalt
262                 when '-i', '--individual'
263                         self.subcommand = :individual
264     when '-l', '--licenses'
265       self.subcommand = :licenses
266     when '-m', '--memoryleak'
267       self.subcommand = :memoryleak
268                 when '-e', '--entities'
269                         self.subcommand = :entities
270                 when '-re'
271                         self.subcommand = :raw_entities
272                 #when /-n([\w_]+)/
273                 #       @entities ||= []
274                 #       @entities << $1.to_sym
275                 #       self.subcommand = :entities unless @subcommand
276                 when '-?', '-h', '--help'
277                         self.subcommand = :help
278                 else
279                         STDERR.puts "Type 'ohcount -?' for usage."
280                         exit 1
281                 end
282         end
283
284         def run!
285                 self.subcommand ||= :summary
286                 if self.respond_to?(self.subcommand)
287                         self.send(self.subcommand)
288                 else
289                         STDERR.puts "Type 'ohcount -?' for usage."
290                         exit 1
291                 end
292         end
293 end
294
295 OhcountCommandLine.new(ARGV).run!