Salome HOME
Add the find_duplicates command
[tools/sat.git] / commands / find_duplicates.py
1 #!/usr/bin/env python
2 #-*- coding:utf-8 -*-
3 #  Copyright (C) 2010-2013  CEA/DEN
4 #
5 #  This library is free software; you can redistribute it and/or
6 #  modify it under the terms of the GNU Lesser General Public
7 #  License as published by the Free Software Foundation; either
8 #  version 2.1 of the License.
9 #
10 #  This library is distributed in the hope that it will be useful,
11 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 #  Lesser General Public License for more details.
14 #
15 #  You should have received a copy of the GNU Lesser General Public
16 #  License along with this library; if not, write to the Free Software
17 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18
19 import os
20
21 import src
22
23 # create a parser for command line options
24 parser = src.options.Options()
25 parser.add_option("s",
26                   "sources",
27                   "boolean",
28                   "sources",
29                   _("Search the duplicate files in the SOURCES directory."))
30 parser.add_option("p",
31                   "path",
32                   "string",
33                   "path",
34                   _("Search the duplicate files in the given directory path."))
35 parser.add_option("",
36                   "exclude-file",
37                   "list2",
38                   "exclude_file",
39                   _("Override the default list of filtered files."))
40 parser.add_option("",
41                   "exclude-extension",
42                   "list2",
43                   "exclude_extension",
44                   _("Override the default list of filtered extensions."))
45 parser.add_option("",
46                   "exclude-path",
47                   "list2",
48                   "exclude_path",
49                   _("Override the default list of filtered pathes."))
50
51 default_extension_ignored = ['html', 'png', 'txt', 'js', 'xml', 'cmake', 'gif', 
52                      'm4', 'in', 'pyo', 'pyc', 'doctree', 'css']
53 default_files_ignored = ['__init__.py', 'Makefile.am', 'VERSION',
54                          'build_configure', 
55                          'README', 'AUTHORS', 'NEWS', 'COPYING', 'ChangeLog']
56 default_directories_ignored = []
57
58 def list_directory(path, extension_ignored, files_ignored, directories_ignored):
59     '''Make the list of all files and paths that are not filtered 
60     
61     :param path Str: The path to of the directory where to search for duplicates
62     :param extension_ignored List: The list of extensions to ignore
63     :param files_ignored List: The list of files to ignore
64     :param directories_ignored List: The list of directory paths to ignore
65     :return: files_arb_out is the list of [file, path] 
66              and files_out is is the list of files
67     :rtype: List, List
68     '''
69     files_out = []
70     files_arb_out=[]
71     for root, __, files in os.walk(path):  
72         for fic in files:
73             extension = fic.split('.')[-1]   
74             if extension not in extension_ignored and fic not in files_ignored:
75                 in_ignored_dir = False
76                 for rep in directories_ignored:
77                     if rep in root:
78                         in_ignored_dir = True                
79                 if not in_ignored_dir:
80                     files_out.append([fic])              
81                     files_arb_out.append([fic, root])
82     return files_arb_out, files_out
83
84 def format_list_of_str(l_str):
85     '''Make a list from a string
86     
87     :param l_str List or Str: The variable to format
88     :return: the formatted variable
89     :rtype: List
90     '''
91     if not isinstance(l_str, list):
92         return l_str
93     return ",".join(l_str)
94
95 def print_info(logger, info, level=2):
96     '''Format a display
97     
98     :param logger Logger: The logger instance
99     :param info List: the list of tuple to display
100     :param valMax float: the maximum value of the variable
101     :param level int: the verbose level that will be used
102     '''
103     smax = max(map(lambda l: len(l[0]), info))
104     for i in info:
105         sp = " " * (smax - len(i[0]))
106         src.printcolors.print_value(logger,
107                                     sp + i[0],
108                                     format_list_of_str(i[1]),
109                                     2)
110     logger.write("\n", level)
111
112 class Progress_bar:
113     "Create a progress bar in the terminal"
114     
115     def __init__(self, name, valMin, valMax, logger, length = 50):
116         '''Initialization of the progress bar.
117         
118         :param name str: The name of the progress bar
119         :param valMin float: the minimum value of the variable
120         :param valMax float: the maximum value of the variable
121         :param logger Logger: the logger instance
122         :param length int: the lenght of the progress bar
123         '''
124         self.name = name
125         self.valMin = valMin
126         self.valMax = valMax
127         self.length = length
128         self.logger = logger
129         if (self.valMax - self.valMin) <= 0 or length <= 0:
130             out_err = _('ERROR: Wrong init values for the progress bar\n')
131             raise src.SatException(out_err)
132         
133     def display_value_progression(self,val):
134         '''Display the progress bar.
135         
136         :param val float: val must be between valMin and valMax.
137         '''
138         if val < self.valMin or val > self.valMax:
139             self.logger.write(src.printcolors.printcWarning(_(
140                            'WARNING : wrong value for the progress bar.\n')), 3)
141         else:
142             perc = (float(val-self.valMin) / (self.valMax - self.valMin)) * 100.
143             nb_equals = int(perc * self.length / 100)
144             out = '\r %s : %3d %% [%s%s]' % (self.name, perc, nb_equals*'=',
145                                              (self.length - nb_equals)*' ' )
146             self.logger.write(out, 3)
147             self.logger.flush()
148
149 def description():
150     '''method that is called when salomeTools is called with --help option.
151     
152     :return: The text to display for the find_duplicates command description.
153     :rtype: str
154     '''
155     return _("The find_duplicates command search recursively for all duplicates"
156              " files in a the INSTALL directory (or the optionally given "
157              "directory) and prints the found files to the terminal.")
158
159 def run(args, runner, logger):
160     '''method that is called when salomeTools is called with find_duplicates 
161        parameter.
162     '''
163     # parse the arguments
164     (options, args) = parser.parse_args(args)
165     
166     # Determine the directory path where to search 
167     # for duplicates files regarding the options
168     if options.path:
169         dir_path = options.path
170     else:
171         src.check_config_has_application(runner.cfg)
172         if options.sources:
173             dir_path = os.path.join(runner.cfg.APPLICATION.workdir, "SOURCES")
174         else:
175             dir_path = os.path.join(runner.cfg.APPLICATION.workdir, "INSTALL")
176     
177     # Get the files to ignore during the searching
178     files_ignored = default_files_ignored
179     if options.exclude_file:
180         files_ignored = options.exclude_file
181
182     # Get the extension to ignore during the searching
183     extension_ignored = default_extension_ignored
184     if options.exclude_extension:
185         extension_ignored = options.exclude_extension
186
187     # Get the directory paths to ignore during the searching
188     directories_ignored = default_directories_ignored
189     if options.exclude_path:
190         directories_ignored = options.exclude_path
191     
192     # Check the directory
193     if not(os.path.isdir(dir_path)):
194         msg = _("%s has to be a valid repository path." % dir_path)
195         logger.write(src.printcolors.printcError(msg), 1)
196         return 1
197     
198     # Display some information
199     info = [(_("Directory"), dir_path),
200             (_("Ignored files"), files_ignored),
201             (_("Ignored extensions"), extension_ignored),
202             (_("Ignored directories"), directories_ignored)
203            ]
204     print_info(logger, info)
205     
206     # Get all the files and paths
207     logger.write(_("Store all file paths ... "), 3)
208     logger.flush()
209     dic, fic = list_directory(dir_path,
210                               extension_ignored,
211                               files_ignored,
212                               directories_ignored)  
213     logger.write(src.printcolors.printcSuccess('OK\n'), 3)
214     
215     # Eliminate all the singletons
216     len_fic = len(fic)
217     range_fic = range(0,len_fic)
218     range_fic.reverse()
219     my_bar = Progress_bar(_('Eliminate the files that are not duplicated'),
220                           0,
221                           len_fic,
222                           logger,
223                           length = 50)
224     for i in range_fic:
225         my_bar.display_value_progression(len_fic - i)
226         if fic.count(fic[i])==1:
227             fic.remove(fic[i])
228             dic.remove(dic[i])
229
230     # Format the resulting variable to get a dictionary
231     logger.write(_("\n\nCompute the dict {files : [list of pathes]} ... "), 3)
232     fic.sort()
233     len_fic = len(fic)
234     rg_fic = range(0,len_fic)
235     rg_fic.reverse()
236     for i in rg_fic:
237         if fic[i-1] != fic[i]:
238             fic.remove(fic[i])
239
240     dic_fic_paths = {}
241     for fichier in fic:
242         the_file = fichier[0]
243         l_path = []
244         for fic_path in dic:
245             if fic_path[0] == the_file:
246                 l_path.append(fic_path[1])
247         dic_fic_paths[the_file] = l_path
248     
249     logger.write(src.printcolors.printcSuccess('OK\n'), 3)
250
251     # End the execution if no duplicates were found
252     if len(dic_fic_paths) == 0:
253         logger.write(_("No duplicate files found.\n"), 3)
254         return 0
255
256     # Check that there are no singletons in the result (it would be a bug)
257     for elem in dic_fic_paths:
258         if len(dic_fic_paths[elem])<2:
259             logger.write(_("Warning : element %s has not more than"
260                          " two paths.\n") % elem, 3)
261
262
263     # Display the results
264     logger.write(src.printcolors.printcInfo(_('\nResults:\n\n')), 3)
265     max_file_name_lenght = max(map(lambda l: len(l), dic_fic_paths.keys()))
266     for fich in dic_fic_paths:
267         logger.write(src.printcolors.printcLabel(fich), 1)
268         sp = " " * (max_file_name_lenght - len(fich))
269         logger.write(sp, 1)
270         for rep in dic_fic_paths[fich]:
271             logger.write(rep, 1)
272             logger.write(" ", 1)
273         logger.write("\n", 1)
274
275     return 0