public inbox for gentoo-commits@lists.gentoo.org
 help / color / mirror / Atom feed
From: "Mike Frysinger" <vapier@gentoo.org>
To: gentoo-commits@lists.gentoo.org
Subject: [gentoo-commits] proj/gentoolkit:gentoolkit-dev commit in: src/ekeyword/tests/profiles/profiles-only/profiles/, /, ...
Date: Mon, 20 Jan 2014 05:39:59 +0000 (UTC)	[thread overview]
Message-ID: <1390192690.840bf7e8df3c86e161f7855a37b008b5cf16f2d8.vapier@gentoo> (raw)

commit:     840bf7e8df3c86e161f7855a37b008b5cf16f2d8
Author:     Mike Frysinger <vapier <AT> gentoo <DOT> org>
AuthorDate: Mon Jan 20 04:38:10 2014 +0000
Commit:     Mike Frysinger <vapier <AT> gentoo <DOT> org>
CommitDate: Mon Jan 20 04:38:10 2014 +0000
URL:        http://git.overlays.gentoo.org/gitweb/?p=proj/gentoolkit.git;a=commit;h=840bf7e8

ekeyword: rewrite in python

A clean rewrite from scratch with unittests all that goodness.

URL: https://bugs.gentoo.org/267565

---
 .gitignore                                         |   1 +
 src/ekeyword/AUTHORS                               |  12 +-
 src/ekeyword/ChangeLog                             |  46 ---
 src/ekeyword/Makefile                              |  20 +-
 src/ekeyword/README                                |  14 +-
 src/ekeyword/ekeyword                              | 246 ------------
 src/ekeyword/ekeyword.pod                          |  68 ----
 src/ekeyword/ekeyword.py                           | 433 +++++++++++++++++++++
 src/ekeyword/ekeyword_unittest.py                  | 368 +++++++++++++++++
 src/ekeyword/tests/process-1.ebuild                |   5 +
 .../tests/profiles/arch-only/profiles/arch.list    |   1 +
 .../tests/profiles/both/profiles/arch.list         |  45 +++
 .../tests/profiles/both/profiles/profiles.desc     | 295 ++++++++++++++
 src/ekeyword/tests/profiles/none/profiles/.keep    |   0
 .../profiles/profiles-only/profiles/profiles.desc  |   1 +
 src/ekeyword2/ekeyword2                            |  96 -----
 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

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


                 reply	other threads:[~2014-01-20  5:40 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1390192690.840bf7e8df3c86e161f7855a37b008b5cf16f2d8.vapier@gentoo \
    --to=vapier@gentoo.org \
    --cc=gentoo-commits@lists.gentoo.org \
    --cc=gentoo-dev@lists.gentoo.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox