summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Frysinger <vapier@gentoo.org>2014-01-19 23:38:10 -0500
committerMike Frysinger <vapier@gentoo.org>2014-01-19 23:38:10 -0500
commit840bf7e8df3c86e161f7855a37b008b5cf16f2d8 (patch)
treede84133e0ace57b86d6f7c6ffeba2d3202f8077b
parentFix echangelog test (diff)
downloadgentoolkit-840bf7e8df3c86e161f7855a37b008b5cf16f2d8.tar.gz
gentoolkit-840bf7e8df3c86e161f7855a37b008b5cf16f2d8.tar.bz2
gentoolkit-840bf7e8df3c86e161f7855a37b008b5cf16f2d8.zip
ekeyword: rewrite in python
A clean rewrite from scratch with unittests all that goodness. URL: https://bugs.gentoo.org/267565
-rw-r--r--.gitignore1
-rw-r--r--src/ekeyword/AUTHORS12
-rw-r--r--src/ekeyword/ChangeLog46
-rw-r--r--src/ekeyword/Makefile20
-rw-r--r--src/ekeyword/README14
-rwxr-xr-xsrc/ekeyword/ekeyword246
-rw-r--r--src/ekeyword/ekeyword.pod68
-rwxr-xr-xsrc/ekeyword/ekeyword.py433
-rwxr-xr-xsrc/ekeyword/ekeyword_unittest.py368
-rw-r--r--src/ekeyword/tests/process-1.ebuild5
l---------src/ekeyword/tests/profiles/arch-only/profiles/arch.list1
-rw-r--r--src/ekeyword/tests/profiles/both/profiles/arch.list45
-rw-r--r--src/ekeyword/tests/profiles/both/profiles/profiles.desc295
-rw-r--r--src/ekeyword/tests/profiles/none/profiles/.keep0
l---------src/ekeyword/tests/profiles/profiles-only/profiles/profiles.desc1
-rwxr-xr-xsrc/ekeyword2/ekeyword296
16 files changed, 1171 insertions, 480 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0d20b64
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*.pyc
diff --git a/src/ekeyword/AUTHORS b/src/ekeyword/AUTHORS
index 3605b38..353a45e 100644
--- a/src/ekeyword/AUTHORS
+++ b/src/ekeyword/AUTHORS
@@ -1,6 +1,10 @@
-Christian Ruppert <idl0r@gentoo.org>
-Paul Varner <fuzzyray@gentoo.org>
-Mike Frysinger <vapier@gentoo.org>
+Current python version:
+ Mike Frysinger <vapier@gentoo.org>
+
+Previous perl version:
+ Christian Ruppert <idl0r@gentoo.org>
+ Paul Varner <fuzzyray@gentoo.org>
+ Mike Frysinger <vapier@gentoo.org>
Original author:
-Aron Griffis <agriffis@gentoo.org>
+ Aron Griffis <agriffis@gentoo.org>
diff --git a/src/ekeyword/ChangeLog b/src/ekeyword/ChangeLog
deleted file mode 100644
index 68d99e5..0000000
--- a/src/ekeyword/ChangeLog
+++ /dev/null
@@ -1,46 +0,0 @@
-24 Apr 2009 Paul Varner <fuzzyray@gentoo.org>
- * Handle multiline KEYWORDS
-
-07 Jan 2009 Mike Frysinger <vapier@gentoo.org>
- * Support intended KEYWORDS
- * Convert every instance of KEYWORDS in the file
- * Error out on invalid arguments #156827
- * Tighten up diff output to show KEYWORDS changes
-
-27 Oct 2005 Aron Griffis <agriffis@gentoo.org>
- * Fix handling of comments
- * Add support for bare ~ as a synonym for ~all
-
-23 Mar 2005 Aron Griffis <agriffis@gentoo.org>
- * Only modify non-masked keywords with "all"
-
-17 Mar 2005 Aron Griffis <agriffis@gentoo.org>
- * Sort keywords alphabetically
-
-09 Nov 2004 Aron Griffis <agriffis@gentoo.org>
- * Fix mismatching of ppc vs. ppc-macos #69683
-
-15 Sep 2004 Aron Griffis <agriffis@gentoo.org>
- * Update for GLEP 22 keywords
- * Change copyright line for Gentoo Foundation
-
-12 Apr 2004 Aron Griffis <agriffis@gentoo.org>
- * Add ability to remove keywords with ^, for example:
- ekeyword ^alpha blah.ebuild
- * Add support for -*, for example: ekeyword -* would add it;
- ekeyword ^* would remove it.
- * Add a man-page for ekeyword
-
-09 Apr 2004 Aron Griffis <agriffis@gentoo.org>
- * Add ability to modify all keywords via all, ~all, or -all, for
- example: ekeyword -all ~alpha ia64 blah.ebuild
-
-31 Mar 2004 Aron Griffis <agriffis@gentoo.org>
- * Fix bug 28426 with patch from Mr_Bones_ to keep ekeyword from confusing
- ppc and ppc64
-
-2004-01-12 Aron Griffis <agriffis@gentoo.org>
- * Allow multiple keywords
-
-2004-01-07 Karl Trygve Kalleberg <karltk@gentoo.org>
- * Added Makefile
diff --git a/src/ekeyword/Makefile b/src/ekeyword/Makefile
index da0116a..cfa1a06 100644
--- a/src/ekeyword/Makefile
+++ b/src/ekeyword/Makefile
@@ -6,21 +6,15 @@
include ../../makedefs.mak
-%.1 : %.pod
- pod2man $< > $@
+.PHONY: all clean dist
+all:
-.PHONY: all
-all: ekeyword.1
-
-dist: ekeyword.1
+dist:
mkdir -p ../../$(DISTDIR)/src/ekeyword
- cp Makefile AUTHORS README ChangeLog ekeyword ekeyword.1 ../../$(DISTDIR)/src/ekeyword/
+ cp Makefile AUTHORS README ekeyword.py ekeyword_unittest.py \
+ ../../$(DISTDIR)/src/ekeyword/
install: all
- install -m 0755 ekeyword $(BINDIR)/
+ install -m 0755 ekeyword.py $(BINDIR)/ekeyword
install -d $(DOCDIR)/ekeyword
- install -m 0644 AUTHORS README ChangeLog $(DOCDIR)/ekeyword/
- install -m 0644 ekeyword.1 $(MAN1DIR)/
-
-clean:
- $(RM) ekeyword.1
+ install -m 0644 AUTHORS README $(DOCDIR)/ekeyword/
diff --git a/src/ekeyword/README b/src/ekeyword/README
index ec7ff5e..b147e4a 100644
--- a/src/ekeyword/README
+++ b/src/ekeyword/README
@@ -1,5 +1,5 @@
Package : ekeyword
-Version : 0.1.0
+Version : 1.0
Author : See AUTHORS
MOTIVATION
@@ -12,9 +12,9 @@ N/A
IMPROVEMENTS
-- Should rewrite to Python, and use Portage directly to select candidate
- ebuilds.
-- Should add entry to ChangeLog.
-
-For improvements, send a mail to agriffis@gentoo.org or make out a bug at
-bugs.gentoo.org.
+- Should we allow users to pass in */-*/~*?
+- Should we collapse multiple globs into one.
+- Should we support multiline KEYWORDS values? No...
+- Support autodetection of ~user homedir expansions.
+ e.g. If "arm" is a user, then "~arm" will be passed in as "/home/arm".
+ We should catch that and normalize it back to "~arm".
diff --git a/src/ekeyword/ekeyword b/src/ekeyword/ekeyword
deleted file mode 100755
index 33ebb4f..0000000
--- a/src/ekeyword/ekeyword
+++ /dev/null
@@ -1,246 +0,0 @@
-#!/usr/bin/perl -w
-#
-# Copyright 2003-2010, Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-# Written by Aron Griffis <agriffis@gentoo.org>
-#
-# ekeyword: Update the KEYWORDS in an ebuild. For example:
-#
-# $ ekeyword ~alpha oaf-0.6.8-r1.ebuild
-# - ppc sparc x86
-# + ~alpha ppc sparc x86
-
-use strict;
-
-my ($kw_re) = '^(?:([-~^]?)(\w[\w-]*)|([-^]\*))$';
-my (@kw);
-
-my $PORTDIR = undef;
-my %ARCH = ();
-
-sub file_parse {
- my $fname = shift;
- my @content = ();
-
- if ( ! -r $fname ) {
- printf STDERR ("Error: File '%s' doesn't exist or is not readable!\n", $fname);
- exit(1);
- }
-
- open(my $fh, '<', $fname);
- while(defined(my $line = <$fh>)) {
- chomp($line);
- $line =~ s/^\s*//g;
- $line =~ s/\s*$//g;
- $line =~ s/\s+/ /g;
- next if length($line) eq 0;
-
- next if $line =~ m/^#/;
-
- push(@content, $line);
- }
- close($fh);
-
- return @content;
-}
-
-sub get_portdir {
- open(my $ph, '-|', 'portageq portdir');
- chomp(my $portdir = <$ph>);
- close($ph);
-
- if (length($portdir) eq 0) {
- printf STDERR ("Error: Couldn't determine your PORTDIR...\n");
- exit(1);
- }
- return $portdir;
-}
-
-sub get_architectures {
- foreach my $arch (file_parse("${PORTDIR}/profiles/arch.list")) {
- $ARCH{$arch} = 0;
- }
-}
-
-sub get_architectures_status {
- foreach my $line (file_parse("${PORTDIR}/profiles/profiles.desc")) {
- my ($arch, undef, $status) = split(/\s/, $line, 3);
-
- if(defined($ARCH{$arch})) {
- $ARCH{$arch} = 1 if $status eq "dev" and $ARCH{$arch} < 3; # Don't override stable
- $ARCH{$arch} = 2 if $status eq "exp" and $ARCH{$arch} < 3; # Don't override stable
- $ARCH{$arch} = 3 if $status eq "stable";
- }
- }
-}
-
-# make sure the cmdline consists of keywords and ebuilds
-unless (@ARGV > 0) {
- # NOTE: ~all will ignore all -arch keywords
- print STDERR "syntax: ekeyword { arch | ~[arch] | -[arch] } ebuild...\n";
- print STDERR "instead of 'arch' you can also use 'all' which covers all existing keywords...\n";
- exit(1);
-}
-for my $a (@ARGV) {
- $a = '~all' if $a eq '~' or $a eq $ENV{'HOME'}; # for vapier
- next if $a =~ /$kw_re/o; # keyword
- next if $a =~ /^\S+\.ebuild$/; # ebuild
- die "I don't understand $a\n";
-}
-
-$PORTDIR = get_portdir();
-get_architectures();
-get_architectures_status();
-
-my $files = 0;
-my $line;
-for my $f (@ARGV) {
- if ($f =~ m/$kw_re/o) {
- my $arch = $2;
-
- if(length($arch) > 0 && $arch ne "all") {
- if(!defined($ARCH{$arch})) {
- printf STDERR ("'%s' is an unknown architecture! skipping...\n", $arch);
- next;
- }
- }
-
- push(@kw, $f);
- next;
- }
-
- print "$f\n";
-
- open(my $fh_in, "<", $f) or die "Can't read $f: $!\n";
- open(my $fh_out, ">", "${f}.new") or die "Can't create ${f}.new: ${!}\n";
-
- my $count = 0;
- while($line = <$fh_in>) {
- $count++ if $line =~ m/^\s*KEYWORDS=/;
- }
- seek($fh_in, 0, 0);
-
- while ($line = <$fh_in>) {
- if ($line =~ m/^\s*KEYWORDS=/) {
-
- # extract the quoted section from KEYWORDS
- while ($line !~ m/^\s*KEYWORDS=["'](?:.+)?["']/) {
- chomp($line);
- my $next = <$fh_in>;
- $line = join(" ", $line, $next);
- }
- (my $quoted = $line) =~ s/^.*?["'](.*?)["'].*/$1/s;
-
- if($count > 1 && length($quoted) eq 0) {
- # Skip empty KEYWORDS variables in case they occur more than
- # once, bug 321475.
- print $fh_out $line;
- next;
- }
-
- # replace -* with -STAR for our convenience below
- $quoted =~ s/-\*/-STAR/;
-
- # Pre sort/unique
- # NOTE: It will not detect duplicates where one is e.g. ~amd64 and
- # one amd64
- my %hash = map { $_, 1 } split(/\s+/, $quoted);
- $quoted = join(" ", keys(%hash));
-
- for my $k (@kw) {
- my ($leader, $arch, $star) = ($k =~ /$kw_re/o);
-
- # handle -* and ^*
- if (defined $star) {
- $leader = substr($star, 0, 1);
- $arch = 'STAR';
- }
-
- # remove keywords
- if ($leader eq '^') {
- if ($arch eq 'all') {
- $quoted = '';
- } else {
- $quoted =~ s/[-~]?\Q$arch\E(\s|$)/$1/;
- }
-
- # add or modify keywords
- } else {
- if ($arch eq 'all') {
- # modify all non-masked keywords in the list
-
- # Don't add stable keywords for != stable architectures
- if(length($leader) eq 0) {
- my @new;
- foreach my $tmp (split(/\s/, $quoted)) {
- my ($_leader, $_arch, undef) = ($tmp =~ m/$kw_re/o);
- $_leader = "" if !defined($_leader);
- $_arch = "" if !defined($_arch);
-
- if($_leader eq "~" && ($ARCH{$_arch} && $ARCH{$_arch} eq 3) ) {
- push(@new, $_arch);
- next;
- }
- else {
- push(@new, "${_leader}${_arch}");
- next;
- }
- }
- $quoted = join(" ", @new);
- }
- else {
- $quoted =~ s/(^|\s)~?(?=\w)/$1$leader/g;
- }
- } else {
- # modify or add keyword
- unless ($quoted =~ s/[-~]?\Q$arch\E(\s|$)/$leader$arch$1/) {
- # modification failed, need to add
- if ($arch eq 'STAR') {
- $quoted = "$leader$arch $quoted";
- } else {
- $quoted .= " $leader$arch";
- }
- }
- }
- }
- }
-
- # replace -STAR with -*
- $quoted =~ s/-STAR\b/-*/;
-
- # sort keywords and fix spacing
- $quoted = join " ", sort {
- (my $sa = $a) =~ s/^\W//;
- (my $sb = $b) =~ s/^\W//;
- return -1 if $sa eq '*';
- return +1 if $sb eq '*';
- $sa .= "-";
- $sb .= "-";
- $sa =~ s/([a-z0-9]+)-([a-z0-9]*)/$2-$1/g;
- $sb =~ s/([a-z0-9]+)-([a-z0-9]*)/$2-$1/g;
- $sa cmp $sb;
- } split(/\s+/, $quoted);
-
- # re-insert quoted to KEYWORDS
- $line =~ s/(["']).*?["']/$1$quoted$1/;
-
- print $fh_out $line or die "Can't write $f.new: $!\n";
- } else {
- print $fh_out $line;
- next;
- }
- }
-
- close($fh_in);
- close($fh_out);
-
- system("diff -U 0 ${f} ${f}.new");
- rename("$f.new", "$f") or die "Can't rename: $!\n";
- $files++;
-}
-
-if ($files == 0) {
- die "No ebuilds processed!";
-}
-
-# vim:ts=4 sw=4
diff --git a/src/ekeyword/ekeyword.pod b/src/ekeyword/ekeyword.pod
deleted file mode 100644
index ff837b9..0000000
--- a/src/ekeyword/ekeyword.pod
+++ /dev/null
@@ -1,68 +0,0 @@
-=head1 NAME
-
-ekeyword - Gentoo: modify package KEYWORDS
-
-=head1 SYNOPSIS
-
-ekeyword { arch|~arch|-arch|^arch } ebuild...
-
-=head1 DESCRIPTION
-
-This tool provides a simple way to add or update KEYWORDS in a set of
-ebuilds. Each command-line argument is processed in order, so that
-keywords are added to the current list as they appear, and ebuilds are
-processed as they appear.
-
-Instead of specifying a specific arch, it's possible to use the word
-"all". This causes the change to apply to all keywords presently
-specified in the ebuild.
-
-The ^ leader instructs ekeyword to remove the specified arch.
-
-=head1 OPTIONS
-
-Presently ekeyword is simple enough that it supplies no options.
-Probably I'll add B<--help> and B<--version> in the future, but for
-now it's enough to track the gentoolkit version.
-
-=head1 EXAMPLES
-
-To mark a single arch stable:
-
- $ ekeyword alpha metalog-0.7-r1.ebuild
- metalog-0.7-r1.ebuild
- -KEYWORDS="~alpha ~amd64 ~hppa ~ia64 ~mips ~ppc ~sparc ~x86"
- +KEYWORDS="alpha ~amd64 ~hppa ~ia64 ~mips ~ppc ~sparc ~x86"
-
-When bumping a package, to mark all arches for testing:
-
- $ ekeyword ~all metalog-0.7-r2.ebuild
- metalog-0.7-r2.ebuild
- -KEYWORDS="alpha amd64 hppa ia64 mips ppc sparc x86"
- +KEYWORDS="~alpha ~amd64 ~hppa ~ia64 ~mips ~ppc ~sparc ~x86"
-
-To signify that a package is broken for all arches except one:
-
- $ ekeyword ^all -* ~x86 metalog-0.7-r3.ebuild
- metalog-0.7-r3.ebuild
- -KEYWORDS="~alpha ~amd64 ~hppa ~ia64 ~mips ~ppc ~sparc ~x86"
- +KEYWORDS="-* ~x86"
-
-To do lots of things at once:
-
- $ ekeyword alpha metalog-0.7-r1.ebuild \
- ~all metalog-0.7-r2.ebuild ^all -* ~x86 metalog-0.7-r3.ebuild
- metalog-0.7-r1.ebuild
- -KEYWORDS="~alpha ~amd64 ~hppa ~ia64 ~mips ~ppc ~sparc ~x86"
- +KEYWORDS="alpha ~amd64 ~hppa ~ia64 ~mips ~ppc ~sparc ~x86"
- metalog-0.7-r2.ebuild
- -KEYWORDS="alpha amd64 hppa ia64 mips ppc sparc x86"
- +KEYWORDS="~alpha ~amd64 ~hppa ~ia64 ~mips ~ppc ~sparc ~x86"
- metalog-0.7-r3.ebuild
- -KEYWORDS="~alpha ~amd64 ~hppa ~ia64 ~mips ~ppc ~sparc ~x86"
- +KEYWORDS="-* ~x86"
-
-=head1 NOTES
-
-This tool was written by Aron Griffis <agriffis@gentoo.org>. Bugs
-found should be filed against me at http://bugs.gentoo.org/
diff --git a/src/ekeyword/ekeyword.py b/src/ekeyword/ekeyword.py
new file mode 100755
index 0000000..37a25ee
--- /dev/null
+++ b/src/ekeyword/ekeyword.py
@@ -0,0 +1,433 @@
+#!/usr/bin/python
+# Copyright 2014 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+# Written by Mike Frysinger <vapier@gentoo.org>
+
+"""Manage KEYWORDS in ebuilds easily.
+
+This tool provides a simple way to add or update KEYWORDS in a set of ebuilds.
+Each command-line argument is processed in order, so that keywords are added to
+the current list as they appear, and ebuilds are processed as they appear.
+
+Instead of specifying a specific arch, it's possible to use the word "all".
+This causes the change to apply to all keywords presently specified in the
+ebuild.
+
+The ^ leader instructs ekeyword to remove the specified arch.
+
+Examples:
+
+ # Mark all existing arches in the ebuild as stable.
+ $ ekeyword all foo-1.ebuild
+
+ # Mark arm as stable and x86 as unstable.
+ $ ekeyword arm ~x86 foo-1.ebuild
+
+ # Mark hppa as unsupported (explicitly adds -hppa).
+ $ ekeyword -hppa foo-1.ebuild
+
+ # Delete alpha keywords from all ebuilds.
+ $ ekeyword ^alpha *.ebuild
+
+ # Mark sparc as stable for foo-1 and m68k as unstable for foo-2.
+ $ ekeyword sparc foo-1.ebuild ~m68k foo-2.ebuild
+
+ # Mark s390 as the same state as amd64.
+ $ ekeyword s390=amd64 foo-1.ebuild
+"""
+
+from __future__ import print_function
+
+import argparse
+import collections
+import difflib
+import os
+import re
+import sys
+
+import portage
+from portage.output import colorize, nocolor
+
+
+VERSION = '1.0 awesome'
+
+Op = collections.namedtuple('Op', ('op', 'arch', 'ref_arch'))
+
+
+def keyword_to_arch(keyword):
+ """Given a keyword, strip it down to its arch value
+
+ When an ARCH shows up in KEYWORDS, it may have prefixes like ~ or -.
+ Strip all that cruft off to get back to the ARCH.
+ """
+ return keyword.lstrip('-~')
+
+
+def sort_keywords(arches):
+ """Sort |arches| list in the order developers expect"""
+ keywords = []
+
+ # Globs always come first.
+ for g in ('-*', '*', '~*'):
+ if g in arches:
+ arches.remove(g)
+ keywords.append(g)
+
+ def arch_cmp(a1, a2):
+ # Sort independent of leading marker (~ or -).
+ a1 = keyword_to_arch(a1)
+ a2 = keyword_to_arch(a2)
+
+ # If a keyword has a "-" in it, then it always comes after ones
+ # that do not. We want things like alpha/mips/sparc showing up
+ # before amd64-fbsd and amd64-linux.
+ if '-' in a1 and not '-' in a2:
+ return 1
+ elif '-' not in a1 and '-' in a2:
+ return -1
+ else:
+ return cmp(a1, a2)
+
+ keywords += sorted(arches, cmp=arch_cmp)
+
+ return keywords
+
+
+def diff_keywords(old_keywords, new_keywords, format='color-inline'):
+ """Show pretty diff between list of keywords"""
+ def show_diff(s):
+ output = ''
+
+ for tag, i0, i1, j0, j1 in s.get_opcodes():
+
+ if tag == 'equal':
+ output += s.a[i0:i1]
+
+ if tag in ('delete', 'replace'):
+ o = s.a[i0:i1]
+ if format == 'color-inline':
+ o = colorize('bg_darkred', o)
+ else:
+ o = '-{%s}' % o
+ output += o
+
+ if tag in ('insert', 'replace'):
+ o = s.b[j0:j1]
+ if format == 'color-inline':
+ o = colorize('bg_darkgreen', o)
+ else:
+ o = '+{%s}' % o
+ output += o
+
+ return output
+
+ sold = ' '.join(old_keywords)
+ snew = ' '.join(new_keywords)
+ s = difflib.SequenceMatcher(str.isspace, sold, snew, autojunk=False)
+ return show_diff(s)
+
+
+def process_keywords(keywords, ops, arch_status=None):
+ """Process |ops| for |keywords|"""
+ new_keywords = set(keywords).copy()
+
+ # Process each op one at a time.
+ for op, oarch, refarch in ops:
+ # Figure out which keywords we need to modify.
+ if oarch == 'all':
+ if not arch_status:
+ raise ValueError('unable to process "all" w/out profiles.desc')
+ old_arches = set([keyword_to_arch(a) for a in new_keywords])
+ if op is None:
+ # Process just stable keywords.
+ arches = [k for k, v in arch_status.items()
+ if v == 'stable' and k in old_arches]
+ else:
+ # Process all possible keywords. We use the arch_status as a
+ # master list. If it lacks some keywords, then we might miss
+ # somethings here, but not much we can do.
+ arches = set(arch_status.keys()) | old_arches
+ else:
+ arches = (oarch,)
+
+ if refarch:
+ # Figure out the state for this arch based on the reference arch.
+ # TODO: Add support for "all" keywords.
+ # XXX: Should this ignore the '-' state ? Does it make sense to
+ # sync e.g. "s390" to "-ppc" ?
+ refkeyword = [x for x in new_keywords if refarch == keyword_to_arch(x)]
+ if not refkeyword:
+ op = '^'
+ elif refkeyword[0].startswith('~'):
+ op = '~'
+ elif refkeyword[0].startswith('-'):
+ op = '-'
+ else:
+ op = None
+
+ # Finally do the actual update of the keywords list.
+ for arch in arches:
+ new_keywords -= set(['%s%s' % (x, arch) for x in ('', '~', '-')])
+
+ if op is None:
+ new_keywords.add(arch)
+ elif op in ('~', '-'):
+ new_keywords.add('%s%s' % (op, arch))
+ elif op == '^':
+ # Already deleted. Whee.
+ pass
+ else:
+ raise ValueError('unknown operation %s' % op)
+
+ return new_keywords
+
+
+def process_content(ebuild, data, ops, arch_status=None, verbose=False,
+ quiet=False, format='color-inline'):
+ """Process |ops| for |data|"""
+ # Set up the user display style based on verbose/quiet settings.
+ if verbose:
+ disp_name = ebuild
+ def logit(msg):
+ print('%s: %s' % (disp_name, msg))
+ elif quiet:
+ def logit(msg):
+ pass
+ else:
+ # Chop the full path and the .ebuild suffix.
+ disp_name = os.path.basename(ebuild)[:-7]
+ def logit(msg):
+ print('%s: %s' % (disp_name, msg))
+
+ # Match any KEYWORDS= entry that isn't commented out.
+ keywords_re = re.compile(r'^([^#]*\bKEYWORDS=)([\'"])(.*)(\2)(.*)')
+ updated = False
+ content = []
+
+ # Walk each line of the ebuild looking for KEYWORDS to process.
+ for line in data:
+ m = keywords_re.match(line)
+ if not m:
+ content.append(line)
+ continue
+
+ # Ok, we've got it, now let's process things.
+ old_keywords = set(m.group(3).split())
+ new_keywords = process_keywords(
+ old_keywords, ops, arch_status=arch_status)
+
+ # Finally let's present the results to the user.
+ if new_keywords != old_keywords:
+ # Only do the diff work if something actually changed.
+ updated = True
+ old_keywords = sort_keywords(old_keywords)
+ new_keywords = sort_keywords(new_keywords)
+ line = '%s"%s"%s\n' % (m.group(1), ' '.join(new_keywords),
+ m.group(5))
+ if format in ('color-inline', 'inline'):
+ logit(diff_keywords(old_keywords, new_keywords, format=format))
+ else:
+ if format == 'long-multi':
+ logit(' '.join(['%*s' % (len(keyword_to_arch(x)) + 1, x)
+ for x in old_keywords]))
+ logit(' '.join(['%*s' % (len(keyword_to_arch(x)) + 1, x)
+ for x in new_keywords]))
+ else:
+ deleted_keywords = [x for x in old_keywords
+ if x not in new_keywords]
+ logit('--- %s' % ' '.join(deleted_keywords))
+ added_keywords = [x for x in new_keywords
+ if x not in old_keywords]
+ logit('+++ %s' % ' '.join(added_keywords))
+
+ content.append(line)
+
+ if not updated:
+ logit('no updates')
+
+ return updated, content
+
+
+def process_ebuild(ebuild, ops, arch_status=None, verbose=False, quiet=False,
+ dry_run=False, format='color-inline'):
+ """Process |ops| for |ebuild|"""
+ with open(ebuild, 'rb') as f:
+ updated, content = process_content(
+ ebuild, f, ops, arch_status=arch_status,
+ verbose=verbose, quiet=quiet, format=format)
+ if updated and not dry_run:
+ with open(ebuild, 'wb') as f:
+ f.writelines(content)
+
+
+def load_profile_data(portdir=None, repo='gentoo'):
+ """Load the list of known arches from the tree"""
+ if portdir is None:
+ portdir = portage.db['/']['vartree'].settings.repositories[repo].location
+
+ arch_status = {}
+
+ try:
+ arch_list = os.path.join(portdir, 'profiles', 'arch.list')
+ with open(arch_list) as f:
+ for line in f:
+ line = line.split('#', 1)[0].strip()
+ if line:
+ arch_status[line] = None
+ except IOError:
+ pass
+
+ try:
+ profile_status = {
+ 'stable': 0,
+ 'dev': 1,
+ 'exp': 2,
+ None: 3,
+ }
+ profiles_list = os.path.join(portdir, 'profiles', 'profiles.desc')
+ with open(profiles_list) as f:
+ for line in f:
+ line = line.split('#', 1)[0].split()
+ if line:
+ arch, profile, status = line
+ arch_status.setdefault(arch, status)
+ curr_status = profile_status[arch_status[arch]]
+ new_status = profile_status[status]
+ if new_status < curr_status:
+ arch_status[arch] = status
+ except IOError:
+ pass
+
+ if arch_status:
+ arch_status['all'] = None
+ else:
+ print('warning: could not read profile files: %s' % arch_list, file=sys.stderr)
+ print('warning: will not be able to verify args are correct', file=sys.stderr)
+
+ return arch_status
+
+
+def arg_to_op(arg):
+ """Convert a command line |arg| to an Op"""
+ arch_prefixes = ('-', '~', '^')
+
+ op = None
+ arch = arg
+ refarch = None
+
+ if arg and arg[0] in arch_prefixes:
+ op, arch = arg[0], arg[1:]
+
+ if '=' in arch:
+ if not op is None:
+ raise ValueError('Cannot use an op and a refarch')
+ arch, refarch = arch.split('=', 1)
+
+ return Op(op, arch, refarch)
+
+
+def args_to_work(args, arch_status=None, repo='gentoo'):
+ """Process |args| into a list of work itmes (ebuild/arches to update)"""
+ work = []
+ todo_arches = []
+ last_todo_arches = None
+
+ for arg in args:
+ if arg.endswith('.ebuild'):
+ if not todo_arches:
+ todo_arches = last_todo_arches
+ if not todo_arches:
+ raise ValueError('missing arches to process for %s' % arg)
+ work.append([arg, todo_arches])
+ last_todo_arches = todo_arches
+ todo_arches = []
+ else:
+ op = arg_to_op(arg)
+ if not arch_status or op.arch in arch_status:
+ todo_arches.append(op)
+ else:
+ raise ValueError('unknown arch/argument: %s' % arg)
+
+ if todo_arches:
+ raise ValueError('missing ebuilds to process!')
+
+ return work
+
+
+def get_parser():
+ """Return an argument parser for ekeyword"""
+ parser = argparse.ArgumentParser(
+ description=__doc__,
+ formatter_class=argparse.RawDescriptionHelpFormatter)
+ parser.add_argument('-n', '--dry-run', default=False, action='store_true',
+ help='Show what would be changed, but do not commit')
+ parser.add_argument('-v', '--verbose', default=False, action='store_true',
+ help='Be verbose while processing things')
+ parser.add_argument('-q', '--quiet', default=False, action='store_true',
+ help='Be quiet while processing things (only show errors)')
+ parser.add_argument('--format', default='auto',
+ choices=('auto', 'color-inline', 'inline', 'short-multi', 'long-multi'),
+ help='Selet output format for showing differences')
+ parser.add_argument('-V', '--version', default=False, action='store_true',
+ help='Show version information')
+ return parser
+
+
+def main(argv):
+ if argv is None:
+ argv = sys.argv[1:]
+
+ # Extract the args ourselves. This is to allow things like -hppa
+ # without tripping over the -h/--help flags. We can't use the
+ # parse_known_args function either.
+ # This sucks and really wish we didn't need to do this ...
+ parse_args = []
+ work_args = []
+ while argv:
+ arg = argv.pop(0)
+ if arg.startswith('--'):
+ if arg == '--':
+ work_args += argv
+ break
+ else:
+ parse_args.append(arg)
+ # Handle flags that take arguments.
+ if arg in ('--format',):
+ if argv:
+ parse_args.append(argv.pop(0))
+ elif arg[0] == '-' and len(arg) == 2:
+ parse_args.append(arg)
+ else:
+ work_args.append(arg)
+
+ parser = get_parser()
+ opts = parser.parse_args(parse_args)
+ if opts.version:
+ print('version: %s' % VERSION)
+ return os.EX_OK
+ if not work_args:
+ parser.error('need arches/ebuilds to process')
+
+ if opts.format == 'auto':
+ if not portage.db['/']['vartree'].settings.get('NOCOLOR', 'false').lower() in ('no', 'false'):
+ nocolor()
+ opts.format = 'short'
+ else:
+ opts.format = 'color-inline'
+
+ arch_status = load_profile_data()
+ try:
+ work = args_to_work(work_args, arch_status=arch_status)
+ except ValueError as e:
+ parser.error(e)
+
+ for ebuild, ops in work:
+ process_ebuild(ebuild, ops, arch_status=arch_status,
+ verbose=opts.verbose, quiet=opts.quiet,
+ dry_run=opts.dry_run, format=opts.format)
+
+ return os.EX_OK
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/src/ekeyword/ekeyword_unittest.py b/src/ekeyword/ekeyword_unittest.py
new file mode 100755
index 0000000..9ccde0e
--- /dev/null
+++ b/src/ekeyword/ekeyword_unittest.py
@@ -0,0 +1,368 @@
+#!/usr/bin/python
+# Copyright 2014 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+# Written by Mike Frysinger <vapier@gentoo.org>
+
+"""Unittests for ekeyword"""
+
+import os
+import tempfile
+import unittest
+
+import ekeyword
+
+
+TESTDIR = os.path.join(os.path.dirname(__file__), 'tests')
+
+
+class TestSortKeywords(unittest.TestCase):
+ """Tests for sort_keywords"""
+
+ def _test(self, input_data, exp_data):
+ output_data = ekeyword.sort_keywords(input_data.split())
+ self.assertEqual(exp_data.split(), output_data)
+
+ def testNull(self):
+ self._test('', '')
+ self._test(' ', '')
+
+ def testGlob(self):
+ self._test('* arm', '* arm')
+ self._test('arm -* x86', '-* arm x86')
+ self._test('hppa ~* amd64', '~* amd64 hppa')
+
+ def testNonLinux(self):
+ self._test('arm-linux alpha amd64-fbsd hppa',
+ 'alpha hppa amd64-fbsd arm-linux')
+
+ def testPrefixes(self):
+ self._test('-hppa arm ~alpha -* ~arm-linux',
+ '-* ~alpha arm -hppa ~arm-linux')
+
+
+class TestDiffKeywords(unittest.TestCase):
+ """Tests for diff_keywords"""
+
+ def testEmpty(self):
+ """Test when there is no content to diff"""
+ ret = ekeyword.diff_keywords([], [])
+ self.assertEqual(ret, '')
+
+ def testSame(self):
+ """Test when there is no difference"""
+ ret = ekeyword.diff_keywords(['a b c'], ['a b c'])
+ self.assertEqual(ret, 'a b c')
+
+ def testInsert(self):
+ """Test when content is simply added"""
+ ret = ekeyword.diff_keywords(['a'], ['~a'])
+ self.assertNotEqual(ret, '')
+
+ def testDelete(self):
+ """Test when content is simply deleted"""
+ ret = ekeyword.diff_keywords(['~a'], ['a'])
+ self.assertNotEqual(ret, '')
+
+ def testReplace(self):
+ """Test when some content replaces another"""
+ ret = ekeyword.diff_keywords(['~a'], ['-a'])
+ self.assertNotEqual(ret, '')
+
+ def _testSmokeFormat(self, format):
+ return ekeyword.diff_keywords(
+ ['~a', 'b', '-abcde'],
+ ['a', '-b', '-abxde'], format=format)
+
+ def testSmokeFormatColor(self):
+ """Run a full smoke test for color-inline format"""
+ ret = self._testSmokeFormat('color-inline')
+ self.assertNotEqual(ret, '')
+
+ def testSmokeFormatNoColor(self):
+ """Run a full smoke test for non-color-inline format"""
+ self._testSmokeFormat('nocolor')
+
+
+class TestProcessKeywords(unittest.TestCase):
+ """Tests for process_keywords"""
+
+ def _test(self, keywords, ops, exp, arch_status=None):
+ # This func doesn't return sorted results (which is fine),
+ # so do so ourselves to get stable tests.
+ ret = ekeyword.process_keywords(
+ keywords.split(), ops, arch_status=arch_status)
+ self.assertEqual(sorted(ret), sorted(exp.split()))
+
+ def testAdd(self):
+ ops = (
+ ekeyword.Op(None, 'arm', None),
+ ekeyword.Op('~', 's390', None),
+ ekeyword.Op('-', 'sh', None),
+ )
+ self._test('moo', ops, 'arm ~s390 -sh moo')
+
+ def testModify(self):
+ ops = (
+ ekeyword.Op(None, 'arm', None),
+ ekeyword.Op('~', 's390', None),
+ ekeyword.Op('-', 'sh', None),
+ )
+ self._test('~arm s390 ~sh moo', ops, 'arm ~s390 -sh moo')
+
+ def testDelete(self):
+ ops = (
+ ekeyword.Op('^', 'arm', None),
+ ekeyword.Op('^', 's390', None),
+ ekeyword.Op('^', 'x86', None),
+ )
+ self._test('arm -s390 ~x86 bar', ops, 'bar')
+
+ def testSync(self):
+ ops = (
+ ekeyword.Op('=', 'arm64', 'arm'),
+ ekeyword.Op('=', 'ppc64', 'ppc'),
+ ekeyword.Op('=', 'amd64', 'x86'),
+ ekeyword.Op('=', 'm68k', 'mips'),
+ ekeyword.Op('=', 'ia64', 'alpha'),
+ ekeyword.Op('=', 'sh', 'sparc'),
+ ekeyword.Op('=', 's390', 's390x'),
+ ekeyword.Op('=', 'boo', 'moo'),
+ )
+ self._test(
+ 'arm64 arm '
+ '~ppc64 ~ppc '
+ '~amd64 x86 '
+ 'm68k ~mips '
+ '-ia64 alpha '
+ 'sh -sparc '
+ 's390 '
+ 'moo ',
+ ops,
+ 'arm64 arm ~ppc64 ~ppc amd64 x86 ~m68k ~mips ia64 alpha '
+ '-sh -sparc boo moo')
+
+ def testAllNoStatus(self):
+ ops = (
+ ekeyword.Op(None, 'all', None),
+ )
+ self.assertRaises(ValueError, self._test, '', ops, '')
+
+ def testAllStable(self):
+ ops = (
+ ekeyword.Op(None, 'all', None),
+ )
+ arch_status = {
+ 'alpha': None,
+ 'arm': 'stable',
+ 'arm64': 'exp',
+ 'm68k': 'dev',
+ }
+ self._test('* ~alpha ~arm ~arm64 ~m68k ~mips ~arm-linux', ops,
+ '* ~alpha arm ~arm64 ~m68k ~mips ~arm-linux', arch_status)
+
+ def testAllUnstable(self):
+ ops = (
+ ekeyword.Op('~', 'all', None),
+ )
+ arch_status = {
+ 'alpha': None,
+ 'arm': 'stable',
+ 'arm64': 'exp',
+ 'm68k': 'dev',
+ }
+ self._test('alpha arm arm64 m68k mips arm-linux', ops,
+ '~alpha ~arm ~arm64 ~m68k ~mips ~arm-linux', arch_status)
+
+ def testAllMultiUnstableStable(self):
+ ops = (
+ ekeyword.Op('~', 'all', None),
+ ekeyword.Op(None, 'all', None),
+ )
+ arch_status = {
+ 'alpha': None,
+ 'arm': 'stable',
+ 'arm64': 'exp',
+ 'm68k': 'dev',
+ }
+ self._test('alpha arm arm64 m68k', ops,
+ '~alpha arm ~arm64 ~m68k', arch_status)
+
+
+class TestProcessContent(unittest.TestCase):
+ """Tests for process_content"""
+
+ def _testKeywords(self, line):
+ ops = (
+ ekeyword.Op(None, 'arm', None),
+ ekeyword.Op('~', 'sparc', None),
+ )
+ return ekeyword.process_content(
+ 'file', ['%s\n' % line], ops, quiet=True)
+
+ def testKeywords(self):
+ """Basic KEYWORDS mod"""
+ updated, ret = self._testKeywords('KEYWORDS=""')
+ self.assertTrue(updated)
+ self.assertEqual(ret, ['KEYWORDS="arm ~sparc"\n'])
+
+ def testKeywordsIndented(self):
+ """Test KEYWORDS indented by space"""
+ updated, ret = self._testKeywords(' KEYWORDS=""')
+ self.assertTrue(updated)
+ self.assertEqual(ret, [' KEYWORDS="arm ~sparc"\n'])
+
+ def testKeywordsSingleQuote(self):
+ """Test single quoted KEYWORDS"""
+ updated, ret = self._testKeywords("KEYWORDS=' '")
+ self.assertTrue(updated)
+ self.assertEqual(ret, ['KEYWORDS="arm ~sparc"\n'])
+
+ def testKeywordsComment(self):
+ """Test commented out KEYWORDS"""
+ updated, ret = self._testKeywords('# KEYWORDS=""')
+ self.assertFalse(updated)
+ self.assertEqual(ret, ['# KEYWORDS=""\n'])
+
+ def testKeywordsCode(self):
+ """Test code leading KEYWORDS"""
+ updated, ret = self._testKeywords('[[ ${PV} ]] && KEYWORDS=""')
+ self.assertTrue(updated)
+ self.assertEqual(ret, ['[[ ${PV} ]] && KEYWORDS="arm ~sparc"\n'])
+
+ def testKeywordsEmpty(self):
+ """Test KEYWORDS not set at all"""
+ updated, ret = self._testKeywords(' KEYWORDS=')
+ self.assertFalse(updated)
+ self.assertEqual(ret, [' KEYWORDS=\n'])
+
+ def _testSmoke(self, format='color-inline', verbose=False, quiet=False):
+ ops = (
+ ekeyword.Op(None, 'arm', None),
+ ekeyword.Op('~', 'sparc', None),
+ )
+ ekeyword.process_content(
+ 'asdf', ['KEYWORDS="arm"'], ops, verbose=verbose,
+ quiet=quiet, format=format)
+
+ def testSmokeQuiet(self):
+ """Smoke test for quiet mode"""
+ self._testSmoke(quiet=True)
+
+ def testSmokeVerbose(self):
+ """Smoke test for verbose mode"""
+ self._testSmoke(verbose=True)
+
+ def testSmokeFormatColor(self):
+ """Smoke test for color-inline format"""
+ self._testSmoke('color-inline')
+
+ def testSmokeFormatInline(self):
+ """Smoke test for inline format"""
+ self._testSmoke('inline')
+
+ def testSmokeFormatShortMulti(self):
+ """Smoke test for short-multi format"""
+ self._testSmoke('short-multi')
+
+ def testSmokeFormatLongMulti(self):
+ """Smoke test for long-multi format"""
+ self._testSmoke('long-multi')
+
+
+class TestProcessEbuild(unittest.TestCase):
+ """Tests for process_ebuild
+
+ This is fairly light as most code is in process_content.
+ """
+
+ def _test(self, dry_run):
+ ops = (
+ ekeyword.Op(None, 'arm', None),
+ ekeyword.Op('~', 'sparc', None),
+ )
+ with tempfile.NamedTemporaryFile() as tmp:
+ with open(tmp.name, 'wb') as fw:
+ with open(os.path.join(TESTDIR, 'process-1.ebuild'), 'rb') as f:
+ orig_content = f.read()
+ fw.write(orig_content)
+ ekeyword.process_ebuild(tmp.name, ops, dry_run=dry_run)
+ with open(tmp.name, 'rb') as f:
+ new_content = f.read()
+ if dry_run:
+ self.assertEqual(orig_content, new_content)
+ else:
+ self.assertNotEqual(orig_content, new_content)
+
+ def testSmokeNotDry(self):
+ self._test(False)
+
+ def testSmokeDry(self):
+ self._test(True)
+
+
+class TestLoadProfileData(unittest.TestCase):
+ """Tests for load_profile_data"""
+
+ def _test(self, subdir):
+ portdir = os.path.join(TESTDIR, 'profiles', subdir)
+ return ekeyword.load_profile_data(portdir=portdir)
+
+ def testLoadBoth(self):
+ """Test loading both arch.list and profiles.desc"""
+ ret = self._test('both')
+ self.assertIn('arm', ret)
+ self.assertEqual(ret['arm'], 'stable')
+ self.assertIn('arm64', ret)
+ self.assertEqual(ret['arm64'], 'exp')
+
+ def testLoadArchOnly(self):
+ """Test loading only arch.list"""
+ ret = self._test('arch-only')
+ self.assertIn('arm', ret)
+ self.assertEqual(ret['arm'], None)
+ self.assertIn('x86-solaris', ret)
+
+ def testLoadProfilesOnly(self):
+ """Test loading only profiles.desc"""
+ ret = self._test('profiles-only')
+ self.assertIn('arm', ret)
+ self.assertEqual(ret['arm'], 'stable')
+ self.assertIn('arm64', ret)
+ self.assertEqual(ret['arm64'], 'exp')
+
+ def testLoadNone(self):
+ """Test running when neither files exists"""
+ ret = self._test('none')
+ self.assertEqual(ret, {})
+
+
+class TestArgToOps(unittest.TestCase):
+
+ def _test(self, arg, op):
+ self.assertEqual(ekeyword.arg_to_op(arg), ekeyword.Op(*op))
+
+ def testStable(self):
+ self._test('arm', (None, 'arm', None))
+
+ def testUnstable(self):
+ self._test('~ppc64', ('~', 'ppc64', None))
+
+ def testDisabled(self):
+ self._test('-sparc', ('-', 'sparc', None))
+
+ def testDeleted(self):
+ self._test('^x86-fbsd', ('^', 'x86-fbsd', None))
+
+ def testSync(self):
+ self._test('s390=x86', (None, 's390', 'x86'))
+
+
+class TestMain(unittest.TestCase):
+
+ def testSmoke(self):
+ ekeyword.main(['arm', '--dry-run', os.path.join(TESTDIR, 'process-1.ebuild')])
+ ekeyword.main(['--version', '--dry-run'])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/ekeyword/tests/process-1.ebuild b/src/ekeyword/tests/process-1.ebuild
new file mode 100644
index 0000000..75168c6
--- /dev/null
+++ b/src/ekeyword/tests/process-1.ebuild
@@ -0,0 +1,5 @@
+# asdf
+
+KEYWORDS="arm ~hppa x86"
+
+# blah
diff --git a/src/ekeyword/tests/profiles/arch-only/profiles/arch.list b/src/ekeyword/tests/profiles/arch-only/profiles/arch.list
new file mode 120000
index 0000000..361ad76
--- /dev/null
+++ b/src/ekeyword/tests/profiles/arch-only/profiles/arch.list
@@ -0,0 +1 @@
+../../both/profiles/arch.list \ No newline at end of file
diff --git a/src/ekeyword/tests/profiles/both/profiles/arch.list b/src/ekeyword/tests/profiles/both/profiles/arch.list
new file mode 100644
index 0000000..e4787c0
--- /dev/null
+++ b/src/ekeyword/tests/profiles/both/profiles/arch.list
@@ -0,0 +1,45 @@
+alpha
+amd64
+amd64-fbsd
+arm
+arm64
+hppa
+ia64
+m68k
+mips
+ppc
+ppc64
+s390
+sh
+sparc
+sparc-fbsd
+x86
+x86-fbsd
+
+# Prefix keywords
+ppc-aix
+x86-freebsd
+x64-freebsd
+sparc64-freebsd
+hppa-hpux
+ia64-hpux
+x86-interix
+amd64-linux
+arm-linux
+ia64-linux
+ppc64-linux
+x86-linux
+ppc-macos
+x86-macos
+x64-macos
+m68k-mint
+x86-netbsd
+ppc-openbsd
+x86-openbsd
+x64-openbsd
+sparc-solaris
+sparc64-solaris
+x64-solaris
+x86-solaris
+x86-winnt
+x86-cygwin
diff --git a/src/ekeyword/tests/profiles/both/profiles/profiles.desc b/src/ekeyword/tests/profiles/both/profiles/profiles.desc
new file mode 100644
index 0000000..be751a8
--- /dev/null
+++ b/src/ekeyword/tests/profiles/both/profiles/profiles.desc
@@ -0,0 +1,295 @@
+#############################################
+# This is a list of valid profiles for each architecture. This file is used by
+# repoman when doing a repoman scan or repoman full.
+# DO NOT ADD PROFILES WITH A "die" or "exit" IN THEM OR IT KILLS REPOMAN
+#
+#layout:
+#arch profile_directory status
+
+# Alpha Profiles
+alpha default/linux/alpha/13.0 stable
+alpha default/linux/alpha/13.0/desktop stable
+alpha default/linux/alpha/13.0/desktop/gnome stable
+alpha default/linux/alpha/13.0/desktop/gnome/systemd stable
+alpha default/linux/alpha/13.0/desktop/kde stable
+alpha default/linux/alpha/13.0/desktop/kde/systemd stable
+alpha default/linux/alpha/13.0/developer stable
+
+# AMD64 Profiles
+amd64 default/linux/amd64/13.0 stable
+amd64 default/linux/amd64/13.0/selinux dev
+amd64 default/linux/amd64/13.0/desktop stable
+amd64 default/linux/amd64/13.0/desktop/gnome stable
+amd64 default/linux/amd64/13.0/desktop/gnome/systemd stable
+amd64 default/linux/amd64/13.0/desktop/kde stable
+amd64 default/linux/amd64/13.0/desktop/kde/systemd stable
+amd64 default/linux/amd64/13.0/developer stable
+amd64 default/linux/amd64/13.0/no-multilib dev
+amd64 default/linux/amd64/13.0/x32 dev
+
+# ARM Profiles
+arm default/linux/arm/13.0 stable
+arm default/linux/arm/13.0/desktop dev
+arm default/linux/arm/13.0/desktop/gnome dev
+arm default/linux/arm/13.0/desktop/gnome/systemd dev
+arm default/linux/arm/13.0/desktop/kde dev
+arm default/linux/arm/13.0/desktop/kde/systemd dev
+arm default/linux/arm/13.0/developer dev
+arm default/linux/arm/13.0/armv4 dev
+arm default/linux/arm/13.0/armv4/desktop dev
+arm default/linux/arm/13.0/armv4/desktop/gnome dev
+arm default/linux/arm/13.0/armv4/desktop/kde dev
+arm default/linux/arm/13.0/armv4/developer dev
+arm default/linux/arm/13.0/armv4t dev
+arm default/linux/arm/13.0/armv4t/desktop dev
+arm default/linux/arm/13.0/armv4t/desktop/gnome dev
+arm default/linux/arm/13.0/armv4t/desktop/kde dev
+arm default/linux/arm/13.0/armv4t/developer dev
+arm default/linux/arm/13.0/armv5te dev
+arm default/linux/arm/13.0/armv5te/desktop dev
+arm default/linux/arm/13.0/armv5te/desktop/gnome dev
+arm default/linux/arm/13.0/armv5te/desktop/kde dev
+arm default/linux/arm/13.0/armv5te/developer dev
+arm default/linux/arm/13.0/armv6j dev
+arm default/linux/arm/13.0/armv6j/desktop dev
+arm default/linux/arm/13.0/armv6j/desktop/gnome dev
+arm default/linux/arm/13.0/armv6j/desktop/kde dev
+arm default/linux/arm/13.0/armv6j/developer dev
+arm default/linux/arm/13.0/armv7a dev
+arm default/linux/arm/13.0/armv7a/desktop dev
+arm default/linux/arm/13.0/armv7a/desktop/gnome dev
+arm default/linux/arm/13.0/armv7a/desktop/kde dev
+arm default/linux/arm/13.0/armv7a/developer dev
+
+# ARM64 Profiles
+arm64 default/linux/arm64/13.0 exp
+arm64 default/linux/arm64/13.0/desktop exp
+arm64 default/linux/arm64/13.0/developer exp
+
+# HPPA Profiles
+hppa default/linux/hppa/13.0 stable
+hppa default/linux/hppa/13.0/desktop dev
+hppa default/linux/hppa/13.0/developer dev
+
+# IA64 Profiles
+ia64 default/linux/ia64/13.0 stable
+ia64 default/linux/ia64/13.0/desktop stable
+ia64 default/linux/ia64/13.0/desktop/gnome stable
+ia64 default/linux/ia64/13.0/desktop/gnome/systemd stable
+ia64 default/linux/ia64/13.0/desktop/kde stable
+ia64 default/linux/ia64/13.0/desktop/kde/systemd stable
+ia64 default/linux/ia64/13.0/developer stable
+
+# M68K Profiles
+m68k default/linux/m68k/13.0 dev
+m68k default/linux/m68k/13.0/desktop dev
+m68k default/linux/m68k/13.0/desktop/gnome dev
+m68k default/linux/m68k/13.0/desktop/kde dev
+m68k default/linux/m68k/13.0/developer dev
+
+# MIPS Profiles
+mips default/linux/mips/13.0 dev
+mips default/linux/mips/13.0/n32 dev
+mips default/linux/mips/13.0/n64 exp
+mips default/linux/mips/13.0/multilib dev
+mips default/linux/mips/13.0/multilib/n32 dev
+mips default/linux/mips/13.0/multilib/n64 exp
+mips default/linux/mips/13.0/mipsel dev
+mips default/linux/mips/13.0/mipsel/n32 dev
+mips default/linux/mips/13.0/mipsel/n64 exp
+mips default/linux/mips/13.0/mipsel/multilib dev
+mips default/linux/mips/13.0/mipsel/multilib/n32 dev
+mips default/linux/mips/13.0/mipsel/multilib/n64 exp
+
+# PPC32 Profiles
+ppc default/linux/powerpc/ppc32/13.0 stable
+ppc default/linux/powerpc/ppc32/13.0/desktop stable
+ppc default/linux/powerpc/ppc32/13.0/desktop/gnome stable
+ppc default/linux/powerpc/ppc32/13.0/desktop/gnome/systemd stable
+ppc default/linux/powerpc/ppc32/13.0/desktop/kde stable
+ppc default/linux/powerpc/ppc32/13.0/desktop/kde/systemd stable
+ppc default/linux/powerpc/ppc32/13.0/developer stable
+
+# PPC64 Profiles
+ppc default/linux/powerpc/ppc64/13.0/32bit-userland stable
+ppc default/linux/powerpc/ppc64/13.0/32bit-userland/desktop stable
+ppc default/linux/powerpc/ppc64/13.0/32bit-userland/desktop/gnome stable
+ppc default/linux/powerpc/ppc64/13.0/32bit-userland/desktop/gnome/systemd stable
+ppc default/linux/powerpc/ppc64/13.0/32bit-userland/desktop/kde stable
+ppc default/linux/powerpc/ppc64/13.0/32bit-userland/desktop/kde/systemd stable
+ppc default/linux/powerpc/ppc64/13.0/32bit-userland/developer stable
+ppc64 default/linux/powerpc/ppc64/13.0/64bit-userland stable
+ppc64 default/linux/powerpc/ppc64/13.0/64bit-userland/desktop stable
+ppc64 default/linux/powerpc/ppc64/13.0/64bit-userland/desktop/gnome stable
+ppc64 default/linux/powerpc/ppc64/13.0/64bit-userland/desktop/gnome/systemd stable
+ppc64 default/linux/powerpc/ppc64/13.0/64bit-userland/desktop/kde stable
+ppc64 default/linux/powerpc/ppc64/13.0/64bit-userland/desktop/kde/systemd stable
+ppc64 default/linux/powerpc/ppc64/13.0/64bit-userland/developer stable
+
+# S390 Profiles
+s390 default/linux/s390/13.0 dev
+s390 default/linux/s390/13.0/s390x dev
+
+# SH Profiles
+sh default/linux/sh/13.0 dev
+sh default/linux/sh/13.0/desktop dev
+sh default/linux/sh/13.0/desktop/gnome dev
+sh default/linux/sh/13.0/desktop/kde dev
+sh default/linux/sh/13.0/developer dev
+
+# SPARC Profiles
+sparc default/linux/sparc/13.0 stable
+sparc default/linux/sparc/13.0/desktop stable
+sparc default/linux/sparc/13.0/desktop/gnome stable
+sparc default/linux/sparc/13.0/desktop/kde stable
+sparc default/linux/sparc/13.0/developer stable
+
+# x86 Profiles
+x86 default/linux/x86/13.0 stable
+x86 default/linux/x86/13.0/selinux dev
+x86 default/linux/x86/13.0/desktop stable
+x86 default/linux/x86/13.0/desktop/gnome stable
+x86 default/linux/x86/13.0/desktop/gnome/systemd stable
+x86 default/linux/x86/13.0/desktop/kde stable
+x86 default/linux/x86/13.0/desktop/kde/systemd stable
+x86 default/linux/x86/13.0/developer stable
+
+# Gentoo/FreeBSD Profiles
+amd64-fbsd default/bsd/fbsd/amd64/9.1 stable
+amd64-fbsd default/bsd/fbsd/amd64/9.2 dev
+amd64-fbsd default/bsd/fbsd/amd64/9.1/clang exp
+amd64-fbsd default/bsd/fbsd/amd64/9.2/clang exp
+sparc-fbsd default/bsd/fbsd/sparc/8.2 exp
+x86-fbsd default/bsd/fbsd/x86/9.1 dev
+x86-fbsd default/bsd/fbsd/x86/9.2 dev
+
+# Hardened Profiles
+amd64 hardened/linux/amd64 stable
+amd64 hardened/linux/amd64/selinux stable
+amd64 hardened/linux/amd64/no-multilib stable
+amd64 hardened/linux/amd64/no-multilib/selinux stable
+amd64 hardened/linux/amd64/x32 dev
+amd64 hardened/linux/uclibc/amd64 dev
+arm hardened/linux/arm/armv7a dev
+arm hardened/linux/arm/armv6j dev
+arm hardened/linux/uclibc/arm/armv7a dev
+ia64 hardened/linux/ia64 dev
+mips hardened/linux/uclibc/mips exp
+mips hardened/linux/uclibc/mips/mipsel exp
+ppc hardened/linux/powerpc/ppc32 dev
+ppc hardened/linux/powerpc/ppc64/32bit-userland dev
+ppc64 hardened/linux/powerpc/ppc64/64bit-userland dev
+x86 hardened/linux/x86 stable
+x86 hardened/linux/x86/selinux stable
+x86 hardened/linux/uclibc/x86 dev
+
+# uclibc/embedded multiarch profiles
+#amd64 uclibc/amd64 dev
+#arm uclibc/arm dev
+#arm uclibc/arm/2.4 dev
+#mips uclibc/mips dev
+#mips uclibc/mips/hardened dev
+#ppc uclibc/ppc dev
+#ppc uclibc/ppc/2.4 dev
+#ppc uclibc/ppc/hardened dev
+#ppc uclibc/ppc/hardened/2.4 dev
+#sh uclibc/sh dev
+#sh uclibc/sh/2.4 dev
+#x86 uclibc/x86 dev
+#x86 uclibc/x86/2.4 dev
+#x86 uclibc/x86/2005.1 dev
+#x86 uclibc/x86/2005.1/2.4 dev
+#x86 uclibc/x86/hardened dev
+#x86 uclibc/x86/hardened/2.4 dev
+
+
+# These are Gentoo Prefix profiles, maintained by the Prefix team
+
+# Linux Profiles
+amd64-linux prefix/linux/amd64 exp
+arm-linux prefix/linux/arm exp
+ia64-linux prefix/linux/ia64 exp
+ppc64-linux prefix/linux/ppc64 exp
+x86-linux prefix/linux/x86 exp
+
+# Mac OS X Profiles
+ppc-macos prefix/darwin/macos/10.4/ppc exp
+x86-macos prefix/darwin/macos/10.4/x86 exp
+ppc-macos prefix/darwin/macos/10.5/ppc exp
+x86-macos prefix/darwin/macos/10.5/x86 exp
+x64-macos prefix/darwin/macos/10.5/x64 exp
+x86-macos prefix/darwin/macos/10.6/x86 exp
+x64-macos prefix/darwin/macos/10.6/x64 exp
+x86-macos prefix/darwin/macos/10.7/x86 exp
+x64-macos prefix/darwin/macos/10.7/x64 exp
+x86-macos prefix/darwin/macos/10.8/x86 exp
+x64-macos prefix/darwin/macos/10.8/x64 exp
+x86-macos prefix/darwin/macos/10.9/x86 exp
+x64-macos prefix/darwin/macos/10.9/x64 exp
+
+# Solaris Profiles
+sparc-solaris prefix/sunos/solaris/5.9/sparc exp
+sparc-solaris prefix/sunos/solaris/5.10/sparc exp
+sparc64-solaris prefix/sunos/solaris/5.10/sparc64 exp
+x86-solaris prefix/sunos/solaris/5.10/x86 exp
+x64-solaris prefix/sunos/solaris/5.10/x64 exp
+sparc-solaris prefix/sunos/solaris/5.11/sparc exp
+sparc64-solaris prefix/sunos/solaris/5.11/sparc64 exp
+x86-solaris prefix/sunos/solaris/5.11/x86 exp
+x64-solaris prefix/sunos/solaris/5.11/x64 exp
+
+# AIX Profiles
+ppc-aix prefix/aix/5.2.0.0/ppc exp
+ppc-aix prefix/aix/5.3.0.0/ppc exp
+ppc-aix prefix/aix/6.1.0.0/ppc exp
+
+# Interix Profiles
+x86-interix prefix/windows/interix/3.5/x86 exp
+x86-interix prefix/windows/interix/5.2/x86 exp
+x86-interix prefix/windows/interix/6.0/x86 exp
+x86-interix prefix/windows/interix/6.1/x86 exp
+
+# Windows Profiles
+x86-winnt prefix/windows/winnt/3.5/x86 exp
+x86-winnt prefix/windows/winnt/5.2/x86 exp
+x86-winnt prefix/windows/winnt/6.0/x86 exp
+x86-winnt prefix/windows/winnt/6.1/x86 exp
+
+# Cygwin Profiles
+x86-cygwin prefix/windows/cygwin/1.7/x86 exp
+
+# HP-UX Profiles
+ia64-hpux prefix/hpux/B.11.23/ia64 exp
+hppa-hpux prefix/hpux/B.11.31/hppa2.0 exp
+ia64-hpux prefix/hpux/B.11.31/ia64 exp
+
+# FreeBSD Profiles
+x86-freebsd prefix/bsd/freebsd/7.1/x86 exp
+x64-freebsd prefix/bsd/freebsd/7.1/x64 exp
+x86-freebsd prefix/bsd/freebsd/7.2/x86 exp
+x64-freebsd prefix/bsd/freebsd/7.2/x64 exp
+x86-freebsd prefix/bsd/freebsd/8.0/x86 exp
+x64-freebsd prefix/bsd/freebsd/8.0/x64 exp
+x86-freebsd prefix/bsd/freebsd/8.1/x86 exp
+x64-freebsd prefix/bsd/freebsd/8.1/x64 exp
+sparc64-freebsd prefix/bsd/freebsd/8.1/sparc64 exp
+x86-freebsd prefix/bsd/freebsd/8.2/x86 exp
+x64-freebsd prefix/bsd/freebsd/8.2/x64 exp
+x86-freebsd prefix/bsd/freebsd/9.0/x86 exp
+x64-freebsd prefix/bsd/freebsd/9.0/x64 exp
+x86-freebsd prefix/bsd/freebsd/9.1/x86 exp
+x64-freebsd prefix/bsd/freebsd/9.1/x64 exp
+
+
+# OpenBSD Profiles
+ppc-openbsd prefix/bsd/openbsd/4.2/ppc exp
+x86-openbsd prefix/bsd/openbsd/4.2/x86 exp
+x64-openbsd prefix/bsd/openbsd/4.2/x64 exp
+
+# NetBSD Profiles
+x86-netbsd prefix/bsd/netbsd/4.0/x86 exp
+
+# FreeMiNT
+m68k-mint prefix/mint/m68k exp
+
+# vim: set ts=8:
diff --git a/src/ekeyword/tests/profiles/none/profiles/.keep b/src/ekeyword/tests/profiles/none/profiles/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/ekeyword/tests/profiles/none/profiles/.keep
diff --git a/src/ekeyword/tests/profiles/profiles-only/profiles/profiles.desc b/src/ekeyword/tests/profiles/profiles-only/profiles/profiles.desc
new file mode 120000
index 0000000..04f8005
--- /dev/null
+++ b/src/ekeyword/tests/profiles/profiles-only/profiles/profiles.desc
@@ -0,0 +1 @@
+../../both/profiles/profiles.desc \ No newline at end of file
diff --git a/src/ekeyword2/ekeyword2 b/src/ekeyword2/ekeyword2
deleted file mode 100755
index ce8842d..0000000
--- a/src/ekeyword2/ekeyword2
+++ /dev/null
@@ -1,96 +0,0 @@
-#!/usr/bin/python
-
-# Output like:
-# setuptools-0.6_rc9.ebuild
-# < KEYWORDS="~alpha ~amd64 ~arm ~hppa ~ia64 ~mips ~ppc ~ppc64 ~s390 ~sh ~sparc ~sparc-fbsd -x86 ~x86-fbsd"
-# ---
-# > KEYWORDS="~alpha ~amd64 ~arm ~hppa ~ia64 ~mips ~ppc ~ppc64 ~s390 ~sh ~sparc ~sparc-fbsd x86 ~x86-fbsd"
-
-from __future__ import with_statement
-from sys import argv
-from fnmatch import fnmatch
-from shutil import copyfile
-from os import environ as env
-
-import re
-import string
-
-from portage import settings
-
-STABLE_KEYWORDS = frozenset(settings["PORTAGE_ARCHLIST"].split())
-BROKEN_KEYWORDS = frozenset(['-*'] + ['-'+k for k in STABLE_KEYWORDS])
-TEST_KEYWORDS = frozenset(['~'+k for k in STABLE_KEYWORDS])
-KNOWN_KEYWORDS = STABLE_KEYWORDS | TEST_KEYWORDS | BROKEN_KEYWORDS
-
-argv = set(argv[1:])
-kw_re = re.compile(r'KEYWORDS="([^"]*)"')
-ebuilds = frozenset([x for x in argv if fnmatch(x, '*.ebuild')])
-pretend = bool(argv.intersection(('-p', '--pretend',)))
-keywords = argv.difference(('-p', '--pretend',)) - ebuilds
-
-if not ebuilds:
- print 'usage: ekeyword [-p|--pretend] [^|~|-][all] [[^|~|-]arch [[^|~|-]arch]...] ebuild [ebuild...]'
-
-for e in ebuilds:
- # TODO: error handling for file I/O
- kw = set(keywords)
- if not pretend:
- try:
- copyfile(e, e+'.orig')
- except IOError:
- print "Can't copy file %s. Check permissions." % e
- exit(1)
- try:
- with open(e) as c:
- ebuild = c.read()
- except IOError:
- print "Can't open file %s. Aborting." % e
- exit(1)
-
- orig = kw_re.search(ebuild)
- curkw = set(orig.groups()[0].split())
-
- # ^ or ^all by itself means remove all keywords
- # (however, other keywords established in the same args still get set.)
- if kw.intersection(('^', '^all',)):
- kw -= set(('^', '^all',))
- curkw = set()
-
- # ~ or ~all by itself means set ~keyword for all keywords
- # since ~ expands to "$HOME" in the shell, assume the user meant ~ if we see
- # the expansion of "$HOME". (Hope there's no user named 'all'.)
- if kw.intersection(('~', '~all', env['HOME'],)):
- kw -= set(('~', '~all', env['HOME'],))
- curkw = set(['~'+k if k in STABLE_KEYWORDS else k for k in curkw])
-
- for k in kw:
- # Remove keywords starting with ^
- if k[0] == '^':
- curkw -= set((k[1:], '-'+k[1:], '~'+k[1:], ))
- # Set ~ and - keywords to TEST and BROKEN, respectively
- elif k[0] == '~' or k[0] == '-':
- curkw -= set((k[1:], '-'+k[1:], '~'+k[1:], ))
- curkw |= set((k,))
- # Set remaining keywords to STABLE
- else:
- curkw -= set(('~'+k,))
- curkw |= set((k,))
-
- # Sort by arch, then OS (Luckily, this makes -* show up first if it's there)
- result = 'KEYWORDS="%s"' % ' '.join(sorted(curkw,
- key=lambda x: x.strip(string.punctuation).lower()))
-
- if not pretend:
- try:
- with open(e, 'w') as rebuild:
- rebuild.write(kw_re.sub(result, ebuild))
- except IOError:
- print "Can't write file %s. Aborting." % e
- exit(1)
-
- unknown_keywords = curkw - KNOWN_KEYWORDS
- if unknown_keywords:
- print "\nWarning: Unknown keywords '%s'.\n" % ', '.join(sorted(unknown_keywords))
-
- print '<<< %s' % orig.group()
- print '>>> %s' % result