Add support for category/* in package.use
[portpeek.git] / portpeek
1 #!/usr/bin/python
2 #
3 # Copyright 2006 Mike Pagano
4 # Distributed under the terms of the GNU General Public License v2
5 #
6 # $Header$
7 # Author: Mike Pagano <mpagano@gentoo.org>
8 #
9 # Portions written ripped from 
10 # - equery, by Karl Trygve Kalleberg <karltk@gentoo.org>
11 # - gentoolkit.py, by Karl Trygve Kalleberg <karltk@gentoo.org>
12 # - portage.py
13 # - emerge
14 #
15 #
16
17 __author__ = "Michael Pagano"
18 __email__ = "mpagano@gentoo.org"
19 __version__ = "2.0.11"
20 __productname__ = "portpeek"
21 __description__ = "Displays user unmasked ebuilds and installable options from the portage tree"
22
23 import sys, os, portage.output, fileinput
24 #import gentoolkit,gentoolkit.helpers,gentoolkit.package,gentoolkit.versionmatch,gentoolkit.query
25 from gentoolkit.versionmatch import VersionMatch,errors
26 from portage.const import USER_CONFIG_PATH
27 from portage.versions import catpkgsplit,pkgcmp,pkgsplit
28 from portage.exception import InvalidAtom
29 from gentoolkit.cpv import CPV
30 from gentoolkit.query import Query
31
32 porttree = portage.db[portage.root]["porttree"]
33 settings = portage.config(clone=portage.settings)
34
35 show_changes_only_flag = False
36 checking_package_unmask = False
37 checking_package_mask = False
38 print_overlay_flag = False
39 info = 0
40 debug = 1
41 logLevel = info
42 show_removable_only_flag = False
43 stable_list = []
44 stable_listNg = [] # handle package.accept_keywords
45 unmask_list = []
46 tilde = 0
47 processing_package_use = False
48 using_gentoo_as_overlay = False
49
50 try:
51     PORTAGE_CONFIGROOT
52 except NameError:
53     PORTAGE_CONFIGROOT="/"
54
55 USER_CONFIG_PATH=PORTAGE_CONFIGROOT + USER_CONFIG_PATH
56
57 #parameters
58 options = [
59 "--keyword",
60 "--unmask",
61 "--mask",
62 "--all",
63 "--changes-only",
64 "--version",
65 "--version",
66 "--help",
67 "--removable-only",
68 "--debug",
69 "--fix", 
70 "--tilde-check",
71 "--no-color",
72 "--package-use"
73 ]
74
75 mappings = {
76 "k":"--keyword",
77 "u":"--unmask",
78 "m":"--mask",
79 "a":"--all",
80 "c":"--changes-only",
81 "V":"--version",
82 "v":"--version",
83 "h":"--help",
84 "r":"--removable-only",
85 "d":"--debug",
86 "f":"--fix", 
87 "t":"--tilde-check",
88 "n":"--no-color",
89 "s":"--package-use"
90 }
91
92 cmdline = []
93 overlays = [settings["PORTDIR_OVERLAY"]]
94
95 def print_usage():
96     # Print full usage information for this tool to the console.
97     print ("\nUsage: " + portage.output.turquoise(__productname__) +  portage.output.yellow(" command "))
98     print ("       " + portage.output.turquoise(__productname__) + portage.output.green(" [ options ]") +  portage.output.yellow(" command "))
99     print ("       " + portage.output.turquoise(__productname__) + portage.output.green(" [-c]") + portage.output.yellow(" [akmu]"))
100     print ("       " + portage.output.turquoise(__productname__) + portage.output.green(" [-r]") + portage.output.yellow(" [akmu]"))
101     print ("       " + portage.output.turquoise(__productname__) + portage.output.green(" [-f]") + portage.output.yellow(" [akmu]"))
102     print ("       " + portage.output.turquoise(__productname__) + portage.output.green(" [-F]") + portage.output.yellow(" [akmu]"))
103     print (portage.output.yellow(" command ") + " can be ")
104     print (portage.output.yellow(" -a, --all") + "       - show all matches")
105     print (portage.output.yellow(" -k, --keyword") + "       - show matches from package.keywords and package.accept_keywords only")
106     print (portage.output.yellow(" -m, --mask") + "      - show matches from package.mask only")
107     print (portage.output.yellow(" -u, --unmask") + "        - show matched from package.unmask only")
108     
109     print (portage.output.yellow(" -f, --fix") + "       - will remove the stabled packages without asking for confirmation")
110     print (portage.output.yellow(" -h, --help") + "      - display this message")
111     print (portage.output.yellow(" -d, --debug") + "         - display more verbose output for debugging")
112     print (portage.output.yellow(" -V, --version") + "       - display version info")
113     print (portage.output.green("options") + " are ")
114     print (portage.output.green(" -c, --changes-only") + \
115         "   - show all matches that have upgrade option, use with " + \
116         "<" + portage.output.yellow(" k ") + "|" + portage.output.yellow(" u ") + \
117         "|" + portage.output.yellow(" m ") + "|" + \
118         portage.output.yellow(" a ") + ">")
119     print (portage.output.green(" -n, --no-color") + \
120         "   - suppress color output")
121     print (portage.output.green(" -r, --removable-only") + \
122         "   - show all matches that can be removed from package files, use with " + \
123         "<" + portage.output.yellow(" k ") + "|" + portage.output.yellow(" u ") + \
124         "|" + portage.output.yellow(" m ") + "|" + \
125         portage.output.yellow(" a ") + ">\n")
126
127 def get_keywords(package, var):
128     mytree = porttree
129     filtered_keywords = ""
130     try:
131         keywords = package.environment("KEYWORDS").split()
132     except KeyError as error:
133         print ("!!! Portpeek caught Exception:", error)
134         print ("!!! This package/version seems to be no longer available, " + \
135         "please check and update/unmerge it") 
136         return "Not Available/Deprecated"
137     
138     #filtered_keywords = filter_keywords(keywords[0])
139     filtered_keywords = filter_keywords(keywords)
140     return filtered_keywords
141
142
143
144 # this is the main function for portpeek
145 # TODO comment this code!
146 def parse_line(line, filename):     
147     global info,debug,using_gentoo_as_overlay
148
149     using_gentoo_as_overlay = False
150     pkgs = None
151     ebuild_output = ""
152     check_pkg = ""
153     not_installed_pkgs = 0
154     pkg_length = 0
155     atom_check="<>="
156
157     print_output(debug,portage.output.blue("Analyzing line: " + line))
158         # determine if we are also check ~ prefixed code
159     if  (tilde == 1): 
160         atom_check="<>=~"
161
162     diffs_found = False
163     display_done = False
164
165     fields = line.replace("\t", " ").split(" ")
166
167     if len(fields) > 0:
168         #if the line does not start with an atom such as (= or >), do not validate 
169         #status as this tool is for check specific versions and not entire packages 
170         # a ~cpv should be handled like an =cpv if requested bythe parameter -t
171         check_pkg = fields[0] # this should be one of <>=~
172         overlay_index = check_pkg.find("::")
173         if ( overlay_index >= 0):
174             overlay_list = check_pkg.rsplit("::")
175             if (len(overlay_list) > 0):
176                 overlay_name = overlay_list[1]
177                 if (overlay_name == "gentoo"):
178                     using_gentoo_as_overlay = True
179             check_pkg = check_pkg[0:check_pkg.find("::")]
180
181         if check_pkg[0] not in atom_check:
182             return
183         if (tilde == 1):
184             if check_pkg[0] in "~":
185                 check_tilde_masked_pkg(check_pkg, filename)
186                 return
187
188         # determine if the package exists
189         try:
190             package_exists = portage.portdb.xmatch("match-all", fields[0])
191         except InvalidAtom:
192             package_exists = False
193
194         if package_exists:
195             # return a Package List based on the cpv
196             query = Query(check_pkg)
197             #query = Query(fields[0])
198             pkgs = []
199
200             try:
201                 pkgs = query.smart_find(True,True,True,True,False,True)
202             except errors.GentoolkitException as err:
203                 pass
204
205             if (pkgs != None):
206                 pkg_length = len(pkgs)
207                 not_installed_pkgs = 0
208                 display_done = False
209
210                                 # go through each package version for a specific version found above
211                 for current_package in pkgs:
212                     if not current_package.is_installed():
213
214                                                 # we have found a package that is in our file, but not installed
215                         not_installed_pkgs = not_installed_pkgs + 1
216
217                         # check to see if specific version of pkg is not installed 
218                         # and display if true
219                         check_pkg = fields[0]
220                         if check_pkg[0] in atom_check:
221                             check_pkg = check_pkg[1:]
222
223                         if (check_pkg == str(current_package.cpv)):
224                             if (not checking_package_mask):
225                                                                 # package is not instaleld
226                                 print_output(info,portage.output.green("\n" + str(current_package.cpv) + ": ") + portage.output.yellow("Not Installed") , current_package, filename)
227                                 if "package.keywords" in filename:
228                                     stable_list.append(str(current_package.cpv))
229                                 if "package.accept_keywords" in filename:
230                                     stable_listNg.append(str(current_package.cpv))
231                                 unmask_list.append(str(current_package.cpv))
232                             else:
233                                                                 # package is masked, and not installed, this is normal use of package.mask
234                                 print_output(info,portage.output.green("" + str(current_package.cpv) + ": ") + portage.output.yellow("Package Masked"),current_package, filename)
235                             display_done = True
236                         continue
237
238                                         # package is installed
239                                         # retrieve the keywords for a file
240                     keywords = "%s" % (get_keywords(current_package,"KEYWORDS").split())
241                     if (keywords.find("Available/Deprecated") >= 0):
242                         continue
243
244                                         #retrieve the mask status of a specific package
245                     pkgmask = _get_mask_status(current_package, False)
246
247                     #determine if installed package is unmasked, if so, display keywords as green
248                     stable = check_for_stable_release(current_package)
249
250                     # do not display if keywords don't exist
251                     if keywords == "[]":
252                         continue
253
254                     if stable:
255                         ebuild_output = portage.output.green("Installed: ") + \
256                             portage.output.turquoise(str(current_package.cpv)) + \
257                             portage.output.green("  Keywords " + keywords)
258                         if "package.unmask" in filename:
259                             unmask_list.append(str(current_package.cpv))
260                         if "package.keywords" in filename:
261                             stable_list.append(str(current_package.cpv))
262                         if "package.accept_keywords" in filename:
263                             stable_listNg.append(str(current_package.cpv))
264                     else:
265                         if (not show_removable_only_flag):
266                             ebuild_output = portage.output.green("Installed: ") + \
267                                 portage.output.turquoise(str(current_package.cpv)) + \
268                                 portage.output.yellow("  Keywords " + keywords) 
269                         else:
270                             ebuild_output = portage.output.yellow(str(current_package.cpv))
271
272                                         # check package.unmask
273                     if (checking_package_unmask):
274                         if (not is_pkg_package_masked(str(current_package.cpv))):
275
276                                                         # package is in package.unmask unnecessarily
277                             ebuild_output = ebuild_output + ": " + portage.output.yellow("Not package masked")
278                             if "package.unmask" in filename:
279                                 unmask_list.append(str(current_package.cpv))
280                                 print_output (info, "" +  ebuild_output,None, filename)
281                                 continue
282
283                                         # print once
284                     ebuild_search_key_printed = False
285                     if stable:
286                         diffs_found = False
287                         ebuild_search_key_printed = True
288                         print_output(info,"\n" + ebuild_output,current_package, filename)
289                     elif not show_changes_only_flag and not show_removable_only_flag:
290                         diffs_found = False
291                         ebuild_search_key_printed = True
292                         print_output(info,"\n" + ebuild_output,current_package)
293
294                                         # go through all versions of a package
295                     query = Query(current_package.category + "/" + current_package.name)
296
297                     all_pkgs = []
298
299                     try:
300                         all_pkgs = query.smart_find(True,True,True,True,False,True)
301                     except errors.GentoolkitException as err:
302                         print_output(debug,portage.output.blue("Package " + current_package.category + "/" + current_package.name + " not found."))
303
304                     for a_package in all_pkgs:
305                         if not a_package.is_installed():
306                                                         # a_package is not installed
307                             pkgmask = _get_mask_status(a_package, False)
308                                                         # print status line of package we are now checking
309                             print_output(debug,portage.output.blue("Checking package: " + str(a_package.cpv) +".pkgmask is " + str(pkgmask)))
310                                                         # if package versions are different
311                             if (VersionMatch(CPV(current_package.cpv)).match(CPV(a_package.cpv))):
312                                 diffs_found = True
313                                 keylist = a_package.environment("KEYWORDS")
314                                 keywords = "%s" % (filter_keywords(keylist)).split()
315                                 #if a_package is masked
316                                 if pkgmask > 0 and not show_removable_only_flag:
317                                     if show_changes_only_flag and not ebuild_search_key_printed:
318                                         print_output (info, "\n" +  ebuild_output, current_package)
319                                         ebuild_search_key_printed = True
320                                         check_for_stable_release(current_package)
321                                     if (pkgmask >= 3):
322                                         print_output (info,portage.output.red("Available: " + str(a_package.cpv) + " [M] Keywords: " + keywords),a_package)
323                                     else:
324                                         print_output (info,portage.output.brown("Available: " + str(a_package.cpv) + " Keywords: " + keywords),a_package)
325                                 else:
326                                     if show_changes_only_flag and not ebuild_search_key_printed:
327                                         print_output (info,"\n" + ebuild_output,current_package)
328                                         ebuild_search_key_printed = True
329                                         print_output(info,portage.output.green("Available: " + str(a_package.cpv) + " Keywords: " + keywords),a_package)
330                 #else:
331                     #print (portage.output.red ("\nCannot find package: " + check_pkg))
332
333                 # if package does not exist, and current_package is None
334                 # then make package using fields[0]
335
336                 # display if pkg/cat is not installed (missing version)
337                 if not_installed_pkgs ==  pkg_length:
338                     if not display_done:
339                         if (not checking_package_mask):
340                             print_output(info,portage.output.green("\n" + fields[0] + ": ") + portage.output.yellow("Not Installed"),current_package)
341                             if "package.keywords" in filename:
342                                 stable_list.append(str(current_package.cpv))
343                             if "package.accept_keywords" in filename:
344                                 stable_listNg.append(str(current_package.cpv))
345                             unmask_list.append(str(current_package.cpv))
346                         else:
347                             print_output (info,portage.output.green("\n" + str(current_package.cpv) + ": ") + portage.output.yellow("Package Masked"),current_package)
348         else:
349             diffs_found = True
350             print (portage.output.red ("\nPackage: " + fields[0] + " not found. Please check " + filename + " to validate entry"))
351             if "package.keywords" in filename:
352                 stable_list.append(fields[0])
353             if "package.accept_keywords" in filename:
354                 stable_listNg.append(fields[0])
355             unmask_list.append(fields[0])
356             show_all_versions(fields[0], filename)
357     current_package = ""
358
359     return diffs_found
360
361 # adding support for etc/portage/package.keywords/<whatever>/package.keywords
362 def get_recursive_info(filename):
363
364     # determine if filename is a directory
365     if os.path.isdir(filename):
366         # get listing of directory
367         filenames = os.listdir(filename)
368         for file_name in filenames:
369             get_recursive_info(filename+os.path.sep+file_name)
370     else:   
371         get_info(filename)
372
373 def get_info(filename):
374
375     diffs_found = False
376     no_file = False
377     filedescriptor = None
378
379     try:
380         filedescriptor = open(filename)
381         for line in filedescriptor.readlines():
382             line = line.strip()
383             if len(line) <= 0:
384                 continue
385             elif line.find("#") >= 0:
386                 # found '#' remove comment
387                 if (skipFile(line,filename)):
388                     return
389                 line = line[0:line.find("#")]
390                 line = line.strip()
391                 if len(line) <= 0:
392                     continue
393             if (processing_package_use == False):
394                 diffs_found = parse_line(line, filename)
395             else:
396                 # process package.use
397                 diffs_found = parse_package_use(line,filename)
398     except IOError:
399         print (portage.output.red("Could not find file " + filename))
400         no_file = True
401
402     if not diffs_found and no_file:
403         print (portage.output.brown("No ebuild options found."))
404
405     # close file
406     if (filedescriptor != None):
407         filedescriptor.close()
408
409 # parse the package.use file and look for packages
410 # not installed
411 def parse_package_use(line, filename):
412     global info,debug
413     pkgs = None
414     check_pkg = ""
415     pkg_length = 0
416     atom_check="<>="
417     any_version = False
418     has_atom = True
419
420     diffs_found = False
421     package_installed = False
422     fields = line.replace("\t", " ").split(" ")
423
424     if len(fields) > 0:
425         check_pkg = fields[0] # this could be one of <>=
426         if check_pkg[0] not in atom_check:
427             has_atom = False
428         else:
429             check_pkg = check_pkg[1:]
430
431
432         # if there is a wildcard, check to make sure at least one
433         # of the packages is installed
434         if check_pkg.find("/*") >= 0:
435             query = Query(check_pkg)
436             pkgs = []
437             try:
438                 pkgs = query.smart_find(True,True,True,True,False,True)
439             except errors.GentoolkitException as err:
440                 print_output(debug,portage.output.blue("parse_package_use: Package " + check_pkg + " not found."))
441                 return False
442
443             if (pkgs != None):
444                 pkg_length = len(pkgs)
445             for current_package in pkgs:
446                 # on wildcard scenario, return False if one package is installed
447                 if current_package.is_installed():
448                     return False
449         else:
450             # look for any version of check_pkg installed as there is 
451             # no version specified in package.use
452             package_exists = portage.portdb.xmatch("match-all", check_pkg)
453             if package_exists:
454                 # get all package versions
455                 query = Query(check_pkg)
456                 pkgs = []
457                 try:
458                     pkgs = query.smart_find(True,True,True,True,False,True)
459                 except errors.GentoolkitException as err:
460                     print_output(debug,portage.output.blue("Package " + check_pkg + " not found."))
461     
462                 if (pkgs != None):
463                     pkg_length = len(pkgs)
464     
465                     # go through each package version for a specific version found above
466                     if (has_atom == False):
467                         for current_package in pkgs:
468                             if current_package.is_installed():
469                                 package_installed = True
470                     else: 
471                         # go through each package version for a specific version found above
472                         for current_package in pkgs:
473                             if (str(current_package.cpv) == check_pkg):
474                                 if not current_package.is_installed():
475                                     print_output(info,portage.output.green("\n" + check_pkg + ": ") + portage.output.yellow("Not Installed"),current_package)
476                                     if "package.keywords" in filename:
477                                         stable_list.append(check_pkg)
478                                     if "package.accept_keywords" in filename:
479                                         stable_listNg.append(check_pkg)
480                                     unmask_list.append(check_pkg)
481                                     return True
482                                 else:
483                                     return False
484             else:
485                 print (portage.output.red ("\nPackage: " + fields[0] + " not found. Please check " + filename + " to validate entry"))
486                 if "package.keywords" in filename:
487                     stable_list.append(check_pkg)
488                 if "package.accept_keywords" in filename:
489                     stable_listNg.append(check_pkg)
490                 unmask_list.append(check_pkg)
491                 return True
492     if (package_installed == False):
493         # package does not exists
494         print_output(info,portage.output.green("\n" + check_pkg + ": ") + portage.output.yellow("Not Installed"),current_package)
495         if "package.keywords" in filename:
496             stable_list.append(check_pkg)
497         if "package.accept_keywords" in filename:
498             stable_listNg.append(check_pkg)
499         unmask_list.append(check_pkg)
500         return True
501
502     return False
503
504
505
506
507 # skip the file if portpeek-skip found in a comment
508 # you can put this in the middle of a file and it will
509 #skip all entrie below it, this is useful to speed things
510 #up by not checking ~kde versions, for example
511 def skipFile (line, filename):
512     if ( (line == None) or (filename == None)):
513         return False
514
515     if line.find("portpeek-skip") >= 0:
516         return True
517
518     return False
519
520 # parse the package.use file and look for packages
521 # not installed
522 def parse_package_use(line, filename):
523
524     global info,debug
525     pkgs = None
526     check_pkg = ""
527     pkg_length = 0
528     atom_check="<>="
529     any_version = False
530     has_atom = True
531
532     diffs_found = False
533     package_installed = False
534     fields = line.replace("\t", " ").split(" ")
535
536     if len(fields) > 0:
537         check_pkg = fields[0] # this could be one of <>=
538         if check_pkg[0] not in atom_check:
539             has_atom = False
540         else:
541             check_pkg = check_pkg[1:]
542
543         # if there is a wildcard, check to make sure at least one
544         # of the packages is installed
545         if check_pkg.find("/*") >= 0:
546             query = Query(check_pkg)
547             pkgs = []
548             try:
549                 pkgs = query.smart_find(True,True,True,True,False,True)
550             except errors.GentoolkitException as err:
551                 print_output(debug,portage.output.blue("parse_package_use: Package " + check_pkg + " not found."))
552                 return False
553
554             if (pkgs != None):
555                 pkg_length = len(pkgs)
556             for current_package in pkgs:
557                 # on wildcard scenario, return False if one package is installed
558                 if current_package.is_installed():
559                     return False
560         else:
561             # look for any version of check_pkg installed as there is 
562             # no version specified in package.use
563
564             package_exists = portage.portdb.xmatch("match-all", check_pkg)
565             if package_exists:
566                 # get all package versions
567                 query = Query(check_pkg)
568                 pkgs = []
569                 try:
570                     pkgs = query.smart_find(True,True,True,True,False,True)
571                 except errors.GentoolkitException as err:
572                         print_output(debug,portage.output.blue("parse_package_use: Package " + check_pkg + " not found."))
573     
574                 if (pkgs != None):
575                     pkg_length = len(pkgs)
576     
577                     # go through each package version for a specific version found above
578                     if (has_atom == False):
579                         for current_package in pkgs:
580                             if current_package.is_installed():
581                                 package_installed = True
582                     else: 
583                         # go through each package version for a specific version found above
584                         for current_package in pkgs:
585                             if (str(current_package.cpv) == check_pkg):
586                                 if not current_package.is_installed():
587                                     print_output(info,portage.output.green("\n" + check_pkg + ": ") + portage.output.yellow("Not Installed"),current_package)
588                                     if "package.keywords" in filename:
589                                         stable_list.append(check_pkg)
590                                     if "package.accept_keywords" in filename:
591                                         stable_listNg.append(check_pkg)
592                                     unmask_list.append(check_pkg)
593                                     return True
594                                 else:
595                                     return False
596             else:
597                 print (portage.output.red ("\nPackage: " + fields[0] + " not found. Please check " + filename + " to validate entry"))
598                 if "package.keywords" in filename:
599                     stable_list.append(check_pkg)
600                 if "package.accept_keywords" in filename:
601                     stable_listNg.append(check_pkg)
602                 unmask_list.append(check_pkg)
603                 return True
604     if (package_installed == False):
605         # package does not exists
606         print_output(info,portage.output.green("\n" + check_pkg + ": ") + portage.output.yellow("Not Installed"),current_package)
607         if "package.keywords" in filename:
608             stable_list.append(check_pkg)
609         if "package.accept_keywords" in filename:
610             stable_listNg.append(check_pkg)
611         unmask_list.append(check_pkg)
612         return True
613
614     return False
615
616
617
618 # parts blatantly stolen from equery
619 # if pure is true, then get "true" mask status that is
620 # not affected by entries in /etc/portage/package.*
621 def _get_mask_status(pkg, pure):
622     pkgmask = 0
623
624     if (pkg == None):
625         return 0
626
627     if pkg.is_masked():
628         pkgmask = pkgmask + 3
629
630     if pure:
631         try:
632             keywords = portage.portdb.aux_get(str(pkg.cpv), ["KEYWORDS"])
633             keywords = keywords[0].split()
634         except KeyError:
635             # cpv does not exist
636             return 0
637     else:
638         keywords = pkg.environment("KEYWORDS").split()
639
640     # first check for stable arch, stop there if it is found
641     if settings["ARCH"] in keywords:
642         return 0
643
644     if "~" + settings["ARCH"] in keywords:
645         pkgmask = pkgmask + 1
646     elif "-*" in keywords or "-" + settings["ARCH"] in keywords:
647         pkgmask = pkgmask + 2
648     
649     return pkgmask
650
651 def is_pkg_package_masked(cpv):
652
653     mysplit = catpkgsplit(cpv)
654     if not mysplit:
655         raise ValueError("invalid CPV: %s" % cpv)
656     if not portage.portdb.cpv_exists(cpv):
657         return False
658
659     mycp = mysplit[0] + "/" + mysplit[1]
660
661     pmaskdict = settings._mask_manager._pmaskdict
662     if mycp in pmaskdict:
663         for package in pmaskdict[mycp]:
664             if cpv in portage.portdb.xmatch("match-all", package):
665                 return True
666                 
667     return False    
668
669 # filter out keywords for archs other than the current one
670 def filter_keywords(keywords):
671     filtered_keywords = ""
672
673     #for key in key_list:
674     for key in keywords:
675         key = key.replace("[", '')
676         key = key.replace("]", "")
677         key = key.replace(",", "")
678         arch=settings["ARCH"]
679
680         # remove '~' from key for comparison
681         key_comparison = key.lstrip('~')
682         if key_comparison == arch:
683             if len(filtered_keywords) != 0:
684                 filtered_keywords = filtered_keywords + " "
685             filtered_keywords = filtered_keywords + key
686         elif "-*" in key:
687             if len(filtered_keywords) != 0:
688                 filtered_keywords = filtered_keywords + " "
689             filtered_keywords = filtered_keywords + key
690
691
692     return filtered_keywords
693
694 # check to see if we have a stable release
695 # in our package.* files that we can remove
696 def check_for_stable_release(pkg):
697     if not is_pkg_package_masked(str(pkg.cpv)):
698         status = _get_mask_status(pkg, True)
699         if status == 0:
700             return True
701     return False
702
703 #print version info
704 def print_version():
705     # Print the version of this tool to the console.
706     print (__productname__ + "(" + __version__ + ") - " + \
707         __description__)
708     print ("Author(s): " + __author__)
709
710 # function to go through a ~cp without a version
711 # and set for removal from file if no masked package version exists
712 def check_tilde_masked_pkg(package_name, filename):
713     ebuild_output=""
714     variable_version = ""
715
716     orig_package_name = package_name
717     package_name = package_name.replace('~','')
718
719     print_output(debug,portage.output.blue("check_tilde_maskd_pkg: orig_package-name is " + orig_package_name))
720     print_output(debug,portage.output.blue("check_tilde_maskd_pkg: package_name is " + package_name))
721
722     query = Query(package_name+"*", True)
723
724     packages = []
725     try:
726         packages = query.smart_find(True,True,True,True,False,True)
727     except errors.GentoolkitException as err:
728         print_output(debug,portage.output.blue("Package " + package_name + " not found."))
729
730     no_versions_installed = True
731     for package in packages:
732         if package.is_installed():
733             no_versions_installed = False
734
735     if (no_versions_installed == True):
736         ebuild_output = portage.output.green("\n" + package_name + ": ") + portage.output.yellow("Not Installed")
737         if "package.unmask" in filename:
738             unmask_list.append(orig_package_name)
739         if "package.keywords" in filename:
740             stable_list.append(orig_package_name)
741         if "package.accept_keywords" in filename:
742             stable_listNg.append(orig_package_name)
743         print (ebuild_output + portage.output.brown(" : " + filename))
744
745         return
746
747     # get all packages matching cat/package
748     if (packages != None):
749         for current_package in packages:
750             print_output(debug,portage.output.blue("check_tilde_maskd_pkg: current_package is " + str(current_package.cpv)))
751             print_output(debug,portage.output.blue("comparing " + package_name + " to " + str(current_package.cpv)))
752             if (pkgcmp(pkgsplit(package_name),pkgsplit(str(current_package.cpv))) <=0 ):
753             #if (pkgcmp(package_name, str(current_package.cpv)) <= 0):
754                 packageObj = gentoolkit.package.Package(str(current_package.cpv))
755                 if (packageObj == None):
756                     # we could not create a package object
757                     return
758                 pkgmask = _get_mask_status(packageObj, True)
759
760                 print_output(debug,portage.output.blue("check_tilde_maskd_pkg: current_package is " + str(current_package.cpv) + " and pkgmask is " + str(pkgmask)))
761
762                 if "package.unmask" in filename:
763                     if (is_pkg_package_masked(str(current_package.cpv))):
764                         # package was found as masked
765                         return
766                     else:
767                         # package is not masked
768                         unmask_list.append(str(current_package.cpv))
769                         ebuild_output = portage.output.yellow(str(current_package.cpv)) + ": " + portage.output.yellow("Not package masked")
770                         print_output(info,ebuild_output, package, filename)
771                         return
772                 else:
773                     if (pkgmask >= 1):
774                         # package was found as masked
775                         return 
776                     else:
777                         # at this point we have no packages >= ~cpv that are masked, present for removal
778                         # package does not have any masked versions
779                         ebuild_output = portage.output.green(package_name + " has no masked versions")
780                         
781                         if "package.unmask" in filename:
782                             unmask_list.append(orig_package_name)
783                         if "package.keywords" in filename:
784                             stable_list.append(orig_package_name)
785                         if "package.accept_keywords" in filename:
786                             stable_listNg.append(orig_package_name)
787                         print_output(info,ebuild_output, package, filename)
788
789 #helper function to print avail pks when version does not exist
790 def show_all_versions(pkg, filename):
791
792     # is package masked
793     is_package_masked = False
794     pkgArr = portage.pkgsplit(pkg)
795
796     if pkgArr is None or len(pkgArr) == 0:
797         return
798
799     # determine if category/package is masked 
800     package = pkgArr[0]
801
802     operator = portage.dep.Atom(pkg).operator
803     if operator is not None:
804         package = package[len(operator):]
805
806     # package is category/package and pkg is category/package-version
807     # is category/package-version we are checking package masked?
808     #if portage.settings.pmaskdict.has_key(package):
809     pmaskdict = settings._mask_manager._pmaskdict
810     if package in pmaskdict:
811         pkg_list = pmaskdict.get(package)
812         # iterate through list array looking for pkg
813         for pkg_check in pkg_list:
814             operator = portage.get_operator(pkg_check)
815             if operator is None:
816                 if pkg_check == package:
817                     is_package_masked = True
818
819     query = Query(package)
820
821     all_pkgs = []
822
823     try:
824         all_pkgs = query.smart_find(True,True,True,True,False,True)
825     except errors.GentoolkitException as err:
826         print_output(debug,portage.output.blue("Package " + package + " not found."))
827
828     for current_package in all_pkgs:
829         keywords = "%s" % (current_package.environment("KEYWORDS").split())
830         keywords = filter_keywords(keywords)
831         keywords = "[%s]" % (keywords)
832         ebuild = current_package.ebuild_path()
833         if ebuild:
834             pkgmask = _get_mask_status(current_package, True)
835             if is_package_masked:
836                 print (portage.output.red("Available: " + str(current_package.cpv) + " [M] Keywords: " + keywords))
837             elif pkgmask > 4:
838                 print (portage.output.red("Available: " + str(current_package.cpv) + " [M] Keywords: " + keywords))
839             elif pkgmask == 4 or pkgmask == 1:
840                 print (portage.output.brown("Available: " + str(current_package.cpv) + " Keywords: " + keywords))
841             else:
842                 print (portage.output.green("Available: " + str(current_package.cpv) + " Keywords: " + keywords))
843                 if "package.keywords" in filename:
844                     stable_list.append(str(current_package.cpv))
845                 if "package.accept_keywords" in filename:
846                     stable_listNg.append(str(current_package.cpv))
847
848 def handle_if_overlay(package):
849     overlay_text = ""
850     global print_overlay_flag,using_gentoo_as_overlay
851
852     if (using_gentoo_as_overlay):
853         return overlay_text
854
855     print_overlay_flag = True
856
857     ebuild_path,overlay_path = porttree.dbapi.findname2(str(package.cpv))
858     index = -1
859     try:
860         index = overlay_list.index(overlay_path)
861     except ValueError as error:
862         overlay_list.append(overlay_path)
863         index = overlay_list.index(overlay_path)
864
865     overlay_text = " [%s]" % (str(index+1))
866
867     return overlay_text
868
869 # if the overlay_text was displayed to the user
870 # we need to display the string at the end 
871 # this array will store the overlays to be displayed
872 def print_overlay_text():
873    
874     global print_overlay_flag
875     
876     if (not print_overlay_flag):
877         return
878     
879     if (len(overlay_list) <= 0):
880         return
881
882     index = 1
883     for x in overlay_list:
884         print (portage.output.turquoise("[" + str(index) + "] ") + x)
885         index = index + 1
886     
887     print ("\n")
888
889 #helper function to print output
890 def print_output(log_level,output_string, package=None, filename=None):
891     
892     global logLevel
893
894     if package != None:
895         if (package.is_overlay()):
896             output_string = "%s%s" % (output_string,portage.output.turquoise(handle_if_overlay(package)))
897             #output_string = output_string + portage.output.turquoise(handle_if_overlay(package))
898         else:
899             if filename != None:
900                 output_string = "%s%s" % (output_string,portage.output.brown(" : " + filename))
901                 #output_string = output_string + portage.output.brown(" : " + filename)
902
903     if (log_level <= logLevel):
904         print (output_string)
905
906 # remove stabled files that are no longer needed from package.keywords
907 # or package.mask
908 # includes support for etc/portage/package.keywords/<whatever>/package.keywords
909 def cleanFile (filename):
910     
911     removeDups = []
912     removed_list = []
913
914     # if the file or directory does not exist
915     # exit out
916     if (os.path.exists(filename) == False):
917         return
918     
919     if "package.keywords" in filename:
920         if ( len(stable_list) == 0):
921             return
922         removeDups = stable_list
923     elif "package.accept_keywords" in filename:
924         if ( len(stable_listNg) == 0):
925             return
926         removeDups = stable_listNg
927     else:
928         if ( len(unmask_list) == 0):
929             return
930         removeDups = unmask_list
931
932     removedDict = {}
933
934     try:
935         # determine if filename is a directory
936         if os.path.isdir(filename):
937             # get listing of directory
938             filenames = os.listdir(filename)
939             for file_name in filenames:
940                 cleanFile(filename+os.path.sep+file_name)
941             return
942         else:   
943             #go through stable array and remove line if found
944             for line in fileinput.input(filename,inplace =1):
945                 itemFound = False
946                 line = line.strip()
947
948                 # make sure line is not empty and do not remove commented out lines
949                 if len(line) <= 0:
950                     continue
951                 elif line.find("#") == 0:
952                     print (line)
953                     continue
954
955                 for item in removeDups:
956                     if item in line:
957                         removed_list.append(item)
958                         removedDict[filename] = item
959                         itemFound = True
960                         removeDups.pop(removeDups.index(item))
961                         break
962                 if (itemFound == False):
963                     print (line)
964             fileinput.close()
965     except OSError as error:
966         print (portage.output.red("Modify/Read access to file: " + filename + " failed: ") ,error)
967     
968     if (len(removed_list) > 0):
969         print ("\n")
970         for package in removed_list:
971             print (portage.output.red("Removing from: ") + portage.output.yellow(filename) + ": " + portage.output.green(package) + "\n")
972
973 # main
974 if __name__ == "__main__":
975
976     if len(sys.argv) == 1:
977         print_usage()
978         sys.exit(1)
979
980     # soooooo stolen from emerge
981     tmpcmdline = sys.argv[1:]
982
983     for cmd in tmpcmdline:
984         if cmd[0:1] == "-" and cmd[1:2] != "-":
985             for cmd_item in cmd[1:]:
986                 if cmd_item in mappings:
987                     if mappings[cmd_item] in cmdline:
988                         print ()
989                         print ("*** Warning: Redundant use of ", mappings[cmd_item])
990                     else:
991                         cmdline.append(mappings[cmd_item])
992                 else:
993                     print ("!!! Error: -"+cmd_item+" is an invalid option.")
994                     sys.exit(-1)
995         else:
996             cmdline.append(cmd)
997
998     #parse long options
999     for cmd in cmdline:
1000         if len(cmd)>=2 and cmd[0:2]=="--":
1001             try:
1002                 i = options.index(cmd)
1003                 continue
1004             except ValueError:
1005                 print ("!!! Error: -"+cmd+" is an invalid option.")
1006                 sys.exit(-1)
1007
1008     if "--changes-only" in cmdline:
1009         cmdline.remove("--changes-only")
1010         show_changes_only_flag = True
1011
1012     if "--removable-only" in cmdline:
1013         cmdline.remove("--removable-only")
1014         show_removable_only_flag = True
1015
1016     if "--debug" in cmdline:
1017         logLevel = debug
1018
1019     if "--no-color" in cmdline:
1020         portage.output.nocolor()
1021
1022     if "--tilde-check" in cmdline:
1023         tilde=1
1024
1025     if "--version" in cmdline:
1026         print_version()
1027         sys.exit(0)
1028     
1029     if "--all" in cmdline:
1030         tmpcmdline = ["--all"]
1031         if "--fix" in cmdline:
1032             tmpcmdline.append("--fix")
1033         elif "--fixwithall" in cmdline:
1034             tmpcmdline.append("--fixwithall")
1035         cmdline=tmpcmdline      
1036
1037     if "--help" in cmdline:
1038         print_usage()
1039         sys.exit(0)
1040     
1041     if (show_changes_only_flag and show_removable_only_flag):
1042         print ("Please select only one of --show-removable (-r) or --changes-only")
1043         print ("Use --help for more info.")
1044         sys.exit(0)
1045         
1046     for cmd in cmdline:
1047         if cmd == "--keyword":
1048             print (portage.output.bold("\npackage.keywords:"))
1049             get_recursive_info(USER_CONFIG_PATH + "/package.keywords")
1050             if "--fix" in cmdline:
1051                 cleanFile(USER_CONFIG_PATH + "/package.keywords")
1052             print (portage.output.bold("Done\n"))
1053             print (portage.output.bold("\npackage.accept_keywords:"))
1054             get_recursive_info(USER_CONFIG_PATH + "/package.accept_keywords")
1055             if "--fix" in cmdline:
1056                 cleanFile(USER_CONFIG_PATH + "/package.accept_keywords")
1057             print (portage.output.bold("Done\n"))
1058         elif cmd == "--unmask": 
1059             print (portage.output.bold("\npackage.unmask:"))
1060             checking_package_unmask = True
1061             get_recursive_info(USER_CONFIG_PATH + "/package.unmask")
1062             checking_package_unmask = False
1063             if "--fix" in cmdline:
1064                 cleanFile(USER_CONFIG_PATH + "/package.unmask")
1065         elif cmd == "--mask":
1066             checking_package_mask = True
1067             print (portage.output.bold("\npackage.mask:"))
1068             get_recursive_info(USER_CONFIG_PATH + "/package.mask")
1069             print (portage.output.bold("Done\n"))
1070             checking_package_mask = False
1071             if "--fix" in cmdline:
1072                 cleanFile(USER_CONFIG_PATH + "/package.mask")
1073         elif cmd == "--package-use":
1074             print (portage.output.bold("\npackage.use:"))
1075             processing_package_use = True
1076             get_recursive_info(USER_CONFIG_PATH + "/package.use")
1077             if "--fix" in cmdline:
1078                 cleanFile(USER_CONFIG_PATH + "/package.use")
1079             print (portage.output.bold("Done\n"))
1080         elif cmd == "--all":
1081             print (portage.output.bold("\npackage.keywords:"))
1082             get_recursive_info(USER_CONFIG_PATH + "/package.keywords")
1083             print (portage.output.bold("\npackage.accept_keywords:"))
1084             get_recursive_info(USER_CONFIG_PATH + "/package.accept_keywords")
1085             print (portage.output.bold("\npackage.unmask:"))
1086             checking_package_unmask = True
1087             get_recursive_info(USER_CONFIG_PATH + "/package.unmask")
1088             checking_package_unmask = False
1089             checking_package_mask = True
1090             print (portage.output.bold("\npackage.mask:"))
1091             get_recursive_info(USER_CONFIG_PATH + "/package.mask")
1092             print (portage.output.bold("\npackage.use:"))
1093             processing_package_use = True
1094             get_recursive_info(USER_CONFIG_PATH + "/package.use")
1095             if "--fix" in cmdline:
1096                 cleanFile(USER_CONFIG_PATH + "/package.keywords")
1097                 cleanFile(USER_CONFIG_PATH + "/package.accept_keywords")
1098                 cleanFile(USER_CONFIG_PATH + "/package.unmask")
1099                 cleanFile(USER_CONFIG_PATH + "/package.use")
1100                 cleanFile(USER_CONFIG_PATH + "/package.mask")
1101             print (portage.output.bold("\nDone\n"))
1102
1103     print_overlay_text()
1104
1105     if len(cmdline) == 0:
1106         if show_changes_only_flag or show_removable_only_flag:
1107             if (show_changes_only_flag):
1108                 print (portage.output.green("-c") + " or " + portage.output.green("--changes-only") + " must be accompanied by one of the following:")
1109             else:
1110                 print (portage.output.green("-r") + " or " + portage.output.green("--removable-only") + " must be accompanied by one of the following:")
1111
1112             print ("     " + portage.output.yellow("-k") + " or " + portage.output.yellow("--keyword"))
1113             print ("     " + portage.output.yellow("-u") + " or " + portage.output.yellow("--unmask"))
1114             print ("     " + portage.output.yellow("-m") + " or " + portage.output.yellow("--mask"))
1115             print ("     " + portage.output.yellow("-a") + " or " + portage.output.yellow("--all"))
1116
1117             print_usage()
1118