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