1#!/usr/bin/ruby
2
3# This file is part of the aMule project.
4#
5# Copyright (c) 2003-2011 aMule Team ( admin@amule.org / http://www.amule.org )
6#
7# This program is free software; you can redistribute it and/or
8# modify it under the terms of the GNU General Public License
9# as published by the Free Software Foundation; either
10# version 2 of the License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
20#
21
22
23# This function returns true if a file is filtered
24# and shound't be included in the sanity testing.
25def IsFiltered(filename)
26	(["./config.h", "./configWIN32.h"].index(filename) != nil) or
27	(filename =~ /^.\/intl\//) or
28	(filename =~ /CryptoPP/)
29end
30
31
32
33# This class represents lines of code, with line-number and text
34# It is used to store the source-files once they have been read
35# and afterwards to store the lines returned by the filters.
36class Line
37	def initialize( number, text )
38		@number = number
39		@text = text
40	end
41
42	attr_reader :number
43	attr_reader :text
44end
45
46
47
48class Result
49	def initialize( type, file, line = nil )
50		@type = type
51		@file = file
52		@line = line
53	end
54
55	def file_name
56		@file.slice( /[^\/]+$/ )
57	end
58
59	def file_path
60		@file.slice( /^.*\// )
61	end
62
63	attr_reader :type
64	attr_reader :file
65	attr_reader :line
66end
67
68
69
70# Base class for Sanity Checkers
71#
72# This class represents the basic sanity-check, which returns all
73# files as positive results, regardless of the contents.
74class SanityCheck
75	def initialize
76		@name = "None"
77		@title = nil
78		@type = "None"
79		@desc = "None"
80		@results = Array.new
81	end
82
83	attr_reader :name
84	attr_reader :type
85	attr_reader :desc
86
87	def title
88		if @title then
89			@title
90		else
91			@name
92		end
93	end
94
95
96	def results
97		@results
98	end
99
100	# This function will be called for each file, with the argument "file" as the
101	# name of the file and the argument "lines" being an array of Line objects for
102	# each line of the file.
103	#
104	def parse_file(file, lines)
105		raise "Missing parse_file() implementation for Filter: #{@name}"
106	end
107
108
109	private
110
111	def add_results( file, lines = [nil] )
112		lines.each do |line|
113			@results << Result.new( self, file, line )
114		end
115	end
116end
117
118
119
120
121
122class CompareAgainstEmptyString < SanityCheck
123	def initialize
124		super
125
126		@name  = "CmpEmptyString"
127		@title = "Comparing With Empty String"
128		@type  = "Good Practice"
129		@desc  = "Comparisons with empty strings, such as wxT(\"\"), wxEmptyString and "
130		@desc += "_(\"\") should be avoided since they force the creation of a temporary "
131		@desc += "string object. The proper method is to use the IsEmpty() member-function "
132		@desc += "of wxString."
133	end
134
135	def parse_file(file, lines)
136		results = lines.select do |line|
137			line.text =~ /[!=]=\s*(wxEmptyString|wxT\(""\)|_\(""\))/ or
138			line.text =~ /(wxEmptyString|wxT\(""\)|_\(""\))\s*[!=]=/
139		end
140
141		add_results( file, results )
142	end
143end
144
145
146
147class AssignmentToEmptyString < SanityCheck
148	def initialize
149		super
150
151		@name  = "EmptyStringAssignment"
152		@title = "Assigning The Empty String"
153		@type  = "Good Practice"
154		@desc  = "Assigning an empty string such as wxT(\"\"), wxEmptyString and _(\"\") "
155		@desc += "to a wxString should be avoided, since it forces the creation of a "
156		@desc += "temporary object which is assigned to the string. The proper way to "
157		@desc += "clear a string is to use the Clear() member-function of wxString."
158	end
159
160	def parse_file(file, lines)
161		if file =~ /\.cpp$/
162			results = lines.select do |line|
163				line.text =~ /[^=!]=\s*(wxEmptyString|wxT\(""\)|_\(""\))/
164			end
165
166			add_results( file, results )
167		end
168	end
169end
170
171
172
173class NoIfNDef < SanityCheck
174	def initialize
175		super
176
177		@name  = "NoIfNDef"
178		@title = "No #ifndef in headerfile"
179		@type  = "Good Practice"
180		@desc  = "All header files should contain a #ifndef __<FILENAME>__. The purpose is to ensure "
181		@desc += "that the header can't be included twice, which would introduce a number of problems."
182	end
183
184	def parse_file(file, lines)
185		if file =~ /\.h$/ then
186			if not lines.find { |x| x.text =~ /^#ifndef.*_H/ } then
187				add_results( file )
188			end
189		end
190	end
191end
192
193
194
195class ThisDeference < SanityCheck
196	def initialize
197		super
198
199		@name  = "ThisDeference"
200		@title = "Dereference of \"this\""
201		@type  = "Good Practice"
202		@desc  = "In all but the case of templates, using \"this->\" is unnecessary and "
203		@desc += "only decreases the readability of the code."
204	end
205
206	def parse_file(file, lines)
207		results = lines.select do |line|
208			line.text =~ /\bthis->/
209		end
210
211		add_results( file, results )
212	end
213end
214
215
216
217class Assert < SanityCheck
218	def initialize
219		super
220
221		@name  = "Assert"
222		@type  = "Consistency"
223		@desc  = "wxASSERT()s should be used rather than normal assert()s "
224		@desc += "for the sake of consistency."
225	end
226
227	def parse_file(file, lines)
228		results = lines.select do |line|
229			line.text =~ /assert\s*\(/
230		end
231
232		add_results( file, results )
233	end
234end
235
236
237
238class WxFailUsage < SanityCheck
239	def initialize
240		super
241
242		@name  = "WxFailUsagesy"
243		@title = "Always failing wxASSERT()"
244		@type  = "Good Practice"
245		@desc  = "Use wxFAIL instead of an always failing wxASSERT()."
246	end
247
248	def parse_file(file, lines)
249		results = lines.select do |line|
250			line.text =~ /\bwxASSERT\s*\(\s*(0|false)\s*\)/ or
251			line.text =~ /\bwxASSERT_MSG\s*\(\s*(0|false)\s*,/
252		end
253
254		add_results( file, results )
255	end
256end
257
258
259
260class PassByValue < SanityCheck
261	def initialize
262		super
263
264		@name  = "PassByValue"
265		@title = "Pass By Value"
266		@type  = "Good Practice"
267		@desc  = "Passing objects by value means an extra overhead for large datatypes. "
268		@desc += "Therefore these should always be passed by const reference when possible. "
269		@desc += "Non-const references should only be used for functions where the function is "
270		@desc += "supposed to change the actual value of the argument and return another or no value."
271	end
272
273	def parse_file(file, lines)
274		results = Array.new
275
276		# Only handle header files
277		if file =~ /\.h$/
278			# Items that should not be passed by value
279			items = [ "wxString", "wxRect", "wxPoint", "CMD4Hash", "CPath" ]
280
281			lines.each do |line|
282				# Try to identify function definitions
283				if line.text =~ /^\s*(virtual|static|inline|)\s*\w+\s+\w+\s*\(.*\)/
284					# Split by arguments
285					if line.text =~ /\(.*\)\s*\{/
286						args = line.text.match(/\(.*\)\s*\{/)[0]
287					else
288						args = line.text.match(/\(.*\)/)[0]
289					end
290					args.split(",").each do |str|
291						items.each do |item|
292							if str =~ /#{item}\s*[^\s\&\*\(]/
293								results.push( line )
294							end
295						end
296					end
297				end
298			end
299		end
300
301		add_results( file, results )
302	end
303end
304
305
306
307class CStr < SanityCheck
308	def initialize
309		super
310
311		@name  = "CStr"
312		@title = "C_Str or GetData"
313		@type  = "Unicoding"
314		@desc  = "Checks for usage of c_str() or GetData(). Using c_str will often result in "
315		@desc += "problems on Unicoded builds and should therefore be avoided. "
316		@desc += "Please note that the GetData check isn't that precise, because many other "
317		@desc += "classes have GetData members, so it does some crude filtering."
318	end
319
320	def parse_file(file, lines)
321		results = lines.select do |line|
322			if line.text =~ /c_str\(\)/ and line.text !~ /char2unicode\(/
323				true
324			else
325				line.text =~ /GetData\(\)/ and line.text =~ /(wxT\(|wxString|_\()/
326			end
327		end
328
329		add_results( file, results )
330	end
331end
332
333
334
335class IfNotDefined < SanityCheck
336	def initialize
337		super
338
339		@name  = "IfDefined"
340		@title = "#if (!)defined"
341		@type  = "Consistency"
342		@desc  = "Use #ifndef or #ifdef instead for reasons of simplicity."
343	end
344
345	def parse_file(file, lines)
346		results = lines.select do |line|
347			if line.text =~ /^#if.*[\!]?defined\(/
348				not line.text =~ /(\&\&|\|\|)/
349			end
350		end
351
352		add_results( file, results )
353	end
354end
355
356
357
358class GPLLicense < SanityCheck
359	def initialize
360		super
361
362		@name  = "MissingGPL"
363		@title = "Missing GPL License"
364		@type  = "License"
365		@desc  = "All header files should contain the proper GPL blorb."
366	end
367
368	def parse_file(file, lines)
369		if file =~ /\.h$/
370			if lines.find { |x| x.text =~ /This (program|library) is free software;/ } == nil
371				add_results( file )
372			end
373		end
374	end
375end
376
377
378
379class Copyright < SanityCheck
380	def initialize
381		super
382
383		@name  = "MissingCopyright"
384		@title = "Missing Copyright Notice"
385		@type  = "License"
386		@desc  = "All files should contain the proper Copyright notice."
387	end
388
389	def parse_file(file, lines)
390		if file =~ /\.h$/
391			found = lines.select do |line|
392				line.text =~ /Copyright\s*\([cC]\)\s*[-\d,]+ aMule (Project|Team)/
393			end
394
395			if found.empty? then
396				add_results( file )
397			end
398		end
399	end
400end
401
402
403
404class PartOfAmule < SanityCheck
405	def initialize
406		super
407
408		@name  = "aMuleNotice"
409		@title = "Missing aMule notice"
410		@type  = "License"
411		@desc  = "All files should contain a notice that they are part of the aMule project."
412	end
413
414	def parse_file(file, lines)
415		if file =~ /\.h$/
416			found = lines.select do |line|
417				line.text =~ /This file is part of the aMule Project/ or
418				line.text =~ /This file is part of aMule/
419			end
420
421			if found.empty? then
422				add_results( file )
423			end
424		end
425	end
426end
427
428
429
430class MissingBody < SanityCheck
431	def initialize
432		super
433
434		@name  = "MissingBody"
435		@title = "Missing Body in Loop"
436		@type  = "Garbage"
437		@desc  = "This check looks for loops without any body. For example \"while(true);\" "
438		@desc += "In most cases this is a sign of either useless code or bugs. Only in a few "
439		@desc += "cases is it valid code, and in those it can often be represented clearer "
440		@desc += "in other ways."
441	end
442
443	def parse_file(file, lines)
444		results = lines.select do |line|
445			if line.text =~ /^[^}]*while\s*\(.*\)\s*;/ or
446			   line.text =~ /^\s*for\s*\(.*\)\s*;[^\)]*$/
447				# Avoid returning "for" spanning multiple lines
448				# TODO A better way to count instances
449				line.text.split("(").size == line.text.split(")").size
450			else
451				false
452			end
453		end
454
455		add_results( file, results )
456	end
457end
458
459
460
461class Translation < SanityCheck
462	def initialize
463		super
464
465		@name  = "Translation"
466		@type  = "Consistency"
467		@desc  = "Calls to AddLogLine should translate the message, whereas "
468		@desc += "calls to AddDebugLogLine shouldn't. This is because the user "
469		@desc += "is meant to see normal log lines, whereas the the debug-lines "
470		@desc += "are only meant for the developers and I don't know about you, but "
471		@desc += "I don't plan on learning every language we choose to translate "
472		@desc += "aMule to. :P"
473	end
474
475	def parse_file(file, lines)
476		results = lines.select do |line|
477			if line.text =~ /\"/
478				line.text =~ /AddLogLine.?.?\(.*wxT\(/ or
479				line.text =~ /AddDebugLogLine.?\(.*_\(/
480			else
481				false
482			end
483		end
484
485		add_results( file, results )
486	end
487end
488
489
490
491class IfZero < SanityCheck
492	def initialize
493		super
494
495		@name  = "PreIfConstant"
496		@title = "#if 0-9"
497		@type  = "Garbage"
498		@desc  = "Disabled code should be removed as soon as possible. If you wish to disable code "
499		@desc += "for only a short period, then please add a comment before the #if. Code with #if [1-9] "
500		@desc += "should be left, but the #ifs removed unless there is a pressing need for them."
501	end
502
503	def parse_file(file, lines)
504		results = lines.select do |line|
505			line.text =~ /#if\s+[0-9]/
506		end
507
508		add_results( file, results )
509	end
510end
511
512
513
514class InlinedIfTrue < SanityCheck
515	def initialize
516		super
517
518		@name  = "InlinedIf"
519		@title = "Inlined If true/false"
520		@type  = "Garbage"
521		@desc  = "Using variations of (x ? true : false) or (x ? false : true) is just plain stupid."
522	end
523
524	def parse_file(file, lines)
525		results = lines.select do |line|
526			line.text =~ /\?\s*(true|false)\s*:\s*(true|false)/i
527		end
528
529		add_results( file, results )
530	end
531end
532
533
534
535class LoopOnConstant < SanityCheck
536	def initialize
537		super
538
539		@name  = "LoopingOnConstant"
540		@title = "Looping On Constant"
541		@type  = "Garbage"
542		@desc  = "This checks detects loops that evaluate constant values "
543		@desc += "(true,false,0..) or \"for\" loops with no conditionals. all "
544		@desc += "are often a sign of poor code and in most cases can be avoided "
545		@desc += "or replaced with more elegant code."
546	end
547
548	def parse_file(file, lines)
549		results = lines.select do |line|
550			line.text =~ /while\s*\(\s*([0-9]+|true|false)\s*\)/i or
551			line.text =~ /for\s*\([^;]*;\s*;[^;]*\)/
552		end
553
554		add_results( file, results )
555	end
556end
557
558
559
560class MissingImplementation < SanityCheck
561	def initialize
562		super
563
564		@name  = "MissingImplementation"
565		@title = "Missing Function Implementation"
566		@type  = "Garbage"
567		@desc  = "Forgetting to remove a function-definition after having removed it "
568		@desc += "from the .cpp file only leads to cluttered header files. The only "
569		@desc += "case where non-implemented functions should be used is when it is "
570		@desc += "necessary to prevent usage of for instance assignment between "
571		@desc += "instances of a class."
572
573		@declarations = Array.new
574		@definitions = Array.new
575	end
576
577	def results
578		@definitions.each do |definition|
579			@declarations.delete_if do |pair|
580				pair.first == definition
581			end
582		end
583
584
585		return @declarations.map do |pair|
586			Result.new( self, pair.last.first, pair.last.last )
587		end
588	end
589
590	def parse_file(file, lines)
591		if file =~ /\.h$/ then
592			level = 0
593			tree = Array.new
594
595			lines.each do |line|
596				level += line.text.count( "{" ) - line.text.count( "}" )
597
598				tree.delete_if do |struct|
599					struct.first > level
600				end
601
602
603				if line.text !~ /^\s*\/\// and line.text !~ /^\s*#/ and line.text !~ /typedef/ then
604					if line.text =~ /^\s*(class|struct)\s+/ and line.text.count( ";" ) == 0 then
605						cur_level = level;
606
607						if line.text.count( "{" ) == 0 then
608							cur_level += 1
609						end
610
611						name = line.text.scan( /^\s*(class|struct)\s+([^\:{;]+)/ )
612						if name != [] then
613							name = name.first.last.strip
614						else
615							name = "Unknown at line " + line.number.to_s + " in " + file
616						end
617
618						tree << [ cur_level, name ]
619					elsif line.text =~ /;\s*$/ and line.text.count( "{" ) == 0 then
620						# No pure virtual functions and no return(blah) calls (which otherwise can fit the requirements)
621						if line.text !~ /=\s*0\s*;\s*$/ and line.text !~ /return/ then
622							re = /^\s*(virtual\s+|static\s+|inline\s+|)\w+(\s+[\*\&]?|[\*\&]\s+)(\w+)\(/.match( line.text )
623
624							if re and level > 0 and tree.last then
625								@declarations << [ tree.last.last + "::" + re[ re.length - 1 ], [ file, line ] ]
626							end
627						end
628					end
629				end
630			end
631		else
632			lines.each do |line|
633				if line.text =~ /\b\w+::\w+\s*\([^;]+$/
634					@definitions << line.text.scan( /\b(\w+::\w+)\s*\([^;]+$/ ).first.first
635				end
636			end
637		end
638	end
639end
640
641
642
643
644
645
646
647# List of enabled filters
648filterList = Array.new
649filterList.push CompareAgainstEmptyString.new
650filterList.push AssignmentToEmptyString.new
651filterList.push NoIfNDef.new
652filterList.push ThisDeference.new
653filterList.push Assert.new
654filterList.push PassByValue.new
655filterList.push CStr.new
656filterList.push IfNotDefined.new
657filterList.push GPLLicense.new
658filterList.push Copyright.new
659filterList.push PartOfAmule.new
660filterList.push MissingBody.new
661filterList.push Translation.new
662filterList.push IfZero.new
663filterList.push InlinedIfTrue.new
664filterList.push LoopOnConstant.new
665filterList.push MissingImplementation.new
666filterList.push WxFailUsage.new
667
668
669# Sort enabled filters by type and name. The reason why this is done here is
670# because it's much easier than manually resorting every time I add a filter
671# or change the name or type of an existing filter.
672filterList.sort! do |x,y|
673	cmp = x.type <=> y.type
674
675	if cmp == 0 then
676		x.title <=> y.title
677	else
678		cmp
679	end
680end
681
682
683
684def parse_files( path, filters )
685	filters = filters.dup
686
687	require "find"
688
689	Find.find( path ) do |filename|
690		if filename =~ /\.(cpp|h)$/ and not IsFiltered(filename) then
691			File.open(filename, "r") do |aFile|
692				# Read lines and add line-numbers
693				lines = Array.new
694				aFile.each_line do |line|
695					lines.push( Line.new( aFile.lineno, line ) )
696				end
697
698				lines.freeze
699
700				# Check the file against each filter
701				filters.each do |filter|
702					# Process the file with this filter
703					filter.parse_file( filename, lines )
704				end
705			end
706		end
707	end
708
709	results = Array.new
710	filters.each do |filter|
711		results += filter.results
712	end
713
714	results
715end
716
717
718
719
720# Helper-function
721def get_val( key, list )
722	if not list.last or list.last.first != key then
723		list << [ key, Array.new ]
724	end
725
726	list.last.last
727end
728
729
730
731def create_result_tree( path, filters )
732	# Gather the results
733	results = parse_files( path, filters )
734
735	# Sort the results by the following sequence of variables: Path -> File -> Filter -> Line
736	results.sort! do |a, b|
737		if (a.file_path <=> b.file_path) == 0 then
738			if (a.file_name <=> b.file_name) == 0 then
739				if (a.type.title <=> b.type.title) == 0 then
740					a.line.number <=> b.line.number
741				else
742					a.type.title <=> b.type.title
743				end
744			else
745				a.file_name <=> b.file_name
746			end
747		else
748			a.file_path <=> b.file_path
749		end
750	end
751
752
753	# Create a tree of results: [ Path, [ File, [ Filter, [ Line ] ] ] ]
754	result_tree = Array.new
755	results.each do |result|
756		get_val( result.type, get_val( result.file_name, get_val( result.file_path, result_tree ) ) ) << result
757	end
758
759
760	result_tree
761end
762
763
764
765def create_filter_tree( filters )
766	# Change the filterList to a tree: [ Type, [ Filter ] ]
767	filter_tree = Array.new
768
769	filters.each do |filter|
770		get_val( filter.type, filter_tree ) << filter
771	end
772
773	filter_tree
774end
775
776
777
778# Converts a number to a string and pads with zeros so that length becomes at least 5
779def PadNum( number )
780	num = number.to_s
781
782	if ( num.size < 5 )
783		( "0" * ( 5 - num.size ) ) + num
784	else
785		num
786	end
787end
788
789
790
791# Helper-function that escapes some chars to HTML codes
792def HTMLEsc( str )
793	str.gsub!( /\&/, "&amp;"  )
794	str.gsub!( /\"/, "&quot;" )
795	str.gsub!( /</,  "&lt;"   )
796	str.gsub!( />/,  "&gt;"   )
797	str.gsub!( /\n/, "<br>"   )
798	str.gsub( /\t/,  "&nbsp;" )
799end
800
801
802
803# Fugly output code goes here
804# ... Abandon hope, yee who read past here
805# TODO Enable use of templates.
806# TODO Abandon hope.
807def OutputHTML( filters, results )
808	text  =
809"<html>
810	<head>
811		<STYLE TYPE=\"text/css\">
812		<!--
813			.dir {
814				background-color: \#A0A0A0;
815				padding-left: 10pt;
816				padding-right: 10pt;
817			}
818			.file {
819				background-color: \#838383;
820				padding-left: 10pt;
821				padding-right: 10pt;
822			}
823			.filter {
824				background-color: #757575;
825				padding-left: 10pt;
826				padding-right: 10pt;
827				padding-top: 5pt;
828			}
829		-->
830		</STYLE>
831	</head>
832	<body bgcolor=\"\#BDBDBD\">
833
834		<h1>Filters</h1>
835		<dl>
836"
837	# List the filters
838	filters.each do |filterType|
839		text +=
840"			<dt><b>#{filterType.first}</b></dt>
841			<dd>
842				<dl>
843"
844		filterType.last.each do |filter|
845			text +=
846"					<dt id=\"#{filter.name}\"><i>#{filter.title}</i></dt>
847					<dd>
848						#{HTMLEsc(filter.desc)}<p>
849					</dd>
850"
851		end
852
853		text +=
854"				</dl>
855			</dd>
856"
857	end
858
859	text +=
860"		</dl>
861
862		<p>
863
864		<h1>Directories</h1>
865		<ul>
866"
867
868	# List the directories
869	results.each do |dir|
870		text +=
871"			<li>
872				<a href=\"\##{dir.first}\">#{dir.first}</a>
873			</li>
874"
875	end
876
877	text +=
878"		</ul>
879
880		<p>
881
882		<h1>Results</h1>
883"
884
885	results.each do |dir|
886		text +=
887"		<div class=\"dir\">
888			<h2 id=\"#{dir.first}\">#{dir.first}</h2>
889"
890
891		dir.last.each do |file|
892			text +=
893"			<div class=\"file\">
894				<h3>#{file.first}</h3>
895
896				<ul>
897"
898
899			file.last.each do |filter|
900				text +=
901"					<li>
902						<div class=\"filter\">
903							<b><a href=\"\##{filter.first.name}\">#{filter.first.title}</a></b>
904
905							<ul>
906"
907
908				filter.last.each do |result|
909					if result.line then
910						text +=
911"								<li><b>#{PadNum(result.line.number)}:</b> #{HTMLEsc(result.line.text.strip)}</li>
912"
913					end
914				end
915
916				text +=
917"							</ul>
918						</div>
919					</li>
920"
921			end
922			text +=
923"				</ul>
924			</div>
925"
926		end
927
928		text +=
929"		</div>
930
931		<p>
932"
933	end
934
935	text +=
936"	</body>
937</html>"
938
939	return text;
940end
941
942
943
944# Columnizing, using the http://www.rubygarden.org/ruby?UsingTestUnit example because I'm lazy
945# TODO Rewrite it to better support newlines and stuff
946def Columnize( text, width, indent )
947	return indent + text.scan(/(.{1,#{width}})(?: |$)/).join("\n#{indent}")
948end
949
950
951
952# Fugly output code also goes here, this is a bit more sparse than the HTML stuff
953def OutputTEXT( filters, results )
954
955	# List the filters
956	text = "Filters\n"
957	filters.each do |filterType|
958		text += "\t* #{filterType.first}\n"
959
960		filterType.last.each do |filter|
961			text += "\t\t- #{filter.title}\n"
962
963			text += Columnize( filter.desc, 80, "\t\t\t" ) + "\n\n"
964		end
965	end
966
967	# List the directories
968	text += "\n\nDirectories\n"
969	results.each do |dir|
970		text += "\t#{dir.first}\n"
971	end
972
973	text += "\n\nResults\n"
974
975	# To avoid bad readability, I only use fullpaths here instead of sections per dir
976	results.each do |dir|
977		dir.last.each do |file|
978			text += "\t#{dir.first}#{file.first}\n"
979
980			file.last.each do |filter|
981				text += "\t\t* #{filter.first.title}\n"
982
983				filter.last.each do |result|
984					if result.line then
985						text += "\t\t\t#{PadNum(result.line.number)}: #{result.line.text.strip}\n"
986					end
987				end
988			end
989
990			text += "\n"
991		end
992	end
993
994	return text;
995end
996
997
998
999#TODO Improved parameter-handling, add =<file> for the outputing to a file
1000ARGV.each do |param|
1001	case param
1002		when "--text" then
1003			puts OutputTEXT( create_filter_tree( filterList ), create_result_tree( ".", filterList ) )
1004		when "--html" then
1005			puts OutputHTML( create_filter_tree( filterList ), create_result_tree( ".", filterList ) )
1006	end
1007end
1008