[arch-commits] Commit in rhythmbox/repos (4 files)

Jan de Groot jgc at archlinux.org
Sun Jun 22 20:53:54 UTC 2008


    Date: Sunday, June 22, 2008 @ 16:53:54
  Author: jgc
Revision: 3480

Merged revisions 3426-3479 via svnmerge from 
svn+ssh://svn.archlinux.org/home/svn-packages/rhythmbox/trunk

........
  r3475 | jgc | 2008-06-22 20:48:32 +0000 (Sun, 22 Jun 2008) | 3 lines
  
  upgpkg: rhythmbox 0.11.5-4
      Suggest correct packages, fix dependencies (FS#10111)
  Fix cover art fetching (FS#10290)
........

Added:
  rhythmbox/repos/extra-x86_64/bgo513851.patch
    (from rev 3475, rhythmbox/trunk/bgo513851.patch)
Modified:
  rhythmbox/repos/extra-x86_64/	(properties)
  rhythmbox/repos/extra-x86_64/PKGBUILD
  rhythmbox/repos/extra-x86_64/rhythmbox.install

-------------------+
 PKGBUILD          |   25 +--
 bgo513851.patch   |  416 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 rhythmbox.install |   18 --
 3 files changed, 437 insertions(+), 22 deletions(-)


Property changes on: rhythmbox/repos/extra-x86_64
___________________________________________________________________
Name: svnmerge-integrated
   - /rhythmbox/trunk:1-3425
   + /rhythmbox/trunk:1-3479

Modified: extra-x86_64/PKGBUILD
===================================================================
--- extra-x86_64/PKGBUILD	2008-06-22 20:52:06 UTC (rev 3479)
+++ extra-x86_64/PKGBUILD	2008-06-22 20:53:54 UTC (rev 3480)
@@ -3,34 +3,37 @@
  
 pkgname=rhythmbox
 pkgver=0.11.5
-pkgrel=3
+pkgrel=4
 pkgdesc="An iTunes-like music player/libary"
 arch=(i686 x86_64)
 license=('GPL')
 url="http://www.rhythmbox.org"
-depends=( 'libgpod>=0.6.0' 'libsoup>=2.4.0' 'gnome-media>=2.22.0'
-          'totem-plparser>=2.22.1' 'musicbrainz>=2.1.5'
-	  'libsexy>=0.1.11' 'gstreamer0.10-mad' 'nautilus-cd-burner>=2.22.0'
-	  'gstreamer0.10-gnomevfs' 'gstreamer0.10-python>=0.10.9'
-	  'desktop-file-utils' 'gnome-python>=2.22.0' 'libmtp>=0.2.4'
-	  'lirc-utils')
+depends=( 'libgpod>=0.6.0' 'libsoup>=2.4.1' 'gnome-media>=2.22.0'
+          'totem-plparser>=2.22.3' 'musicbrainz>=2.1.5'
+	  'libsexy>=0.1.11' 'nautilus-cd-burner>=2.22.1' 'libmtp>=0.2.4'
+	  'lirc-utils' 'desktop-file-utils' 'gnome-python>=2.22.1'
+	  'gstreamer0.10-python>=0.10.12' 'gstreamer0.10-base-plugins'
+	  'gstreamer0.10-good-plugins')
 makedepends=('perlxml' 'pkgconfig' 'gnome-doc-utils>=0.12.2' 'xulrunner>=1.9') 
 options=('!libtool' '!emptydirs')
 install=rhythmbox.install
 source=(http://ftp.gnome.org/pub/GNOME/sources/${pkgname}/0.11/${pkgname}-${pkgver}.tar.bz2
-	rb-shell.patch)
-md5sums=('967440dd984ec724e7e7992d5bd57bbd' '9ff9492fe5a6580620ed4a177abc6296')
+	rb-shell.patch
+	bgo513851.patch)
+md5sums=('967440dd984ec724e7e7992d5bd57bbd' '9ff9492fe5a6580620ed4a177abc6296'
+	 '94aaeecc50a7551b1eb1b1f3e40bc2cd')
 
 build() {
   cd ${startdir}/src/${pkgname}-${pkgver}
   patch -Np1 -i ${startdir}/src/rb-shell.patch || return 1
+  patch -Np1 -i ${startdir}/src/bgo513851.patch || return 1
   ./configure --prefix=/usr --sysconfdir=/etc \
               --libexecdir=/usr/lib/rhythmbox \
               --localstatedir=/var --disable-static \
 	      --with-cd-burner \
 	      --with-playback=gstreamer-0-10 --enable-daap \
               --with-mdns=avahi --disable-scrollkeeper \
-              --enable-python
+              --enable-python || return 1
   
   make || return 1
   make GCONF_DISABLE_MAKEFILE_SCHEMA_INSTALL=1 DESTDIR=${startdir}/pkg install || return 1
@@ -40,5 +43,5 @@
   rm -f ${startdir}/pkg/etc/gconf/schemas/*.schemas
 
   # fix missing artdisplay artwork
-  install -m 644 plugins/artdisplay/rhythmbox-missing-artwork.svg ${startdir}/pkg/usr/lib/rhythmbox/plugins/artdisplay/
+  #install -m 644 plugins/artdisplay/rhythmbox-missing-artwork.svg ${startdir}/pkg/usr/lib/rhythmbox/plugins/artdisplay/ || return 1
 }

Copied: rhythmbox/repos/extra-x86_64/bgo513851.patch (from rev 3475, rhythmbox/trunk/bgo513851.patch)
===================================================================
--- extra-x86_64/bgo513851.patch	                        (rev 0)
+++ extra-x86_64/bgo513851.patch	2008-06-22 20:53:54 UTC (rev 3480)
@@ -0,0 +1,416 @@
+Update the Amazon cover fetcher to use the new ECS 4.0 API
+---
+ .../artdisplay/artdisplay/AmazonCoverArtSearch.py  |   93 +++++++++-----------
+ 1 files changed, 42 insertions(+), 51 deletions(-)
+
+diff --git a/plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py b/plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py
+index 8fb3e78..489d302 100644
+--- a/plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py
++++ b/plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py
+@@ -35,11 +35,13 @@ class AmazonCoverArtSearch (object):
+ 		self.searching = False
+ 		self.cancel = False
+ 		self.loader = loader
++		# "JP is the only locale that correctly takes UTF8 input. All other locales use LATIN1."
++		# http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1295&categoryID=117
+ 		self._supportedLocales = {
+-			"en_US" : ("us", "xml.amazon.com", "music"),
+-			"en_GB" : ("uk", "xml-eu.amazon.com", "music"),
+-			"de" : ("de", "xml-eu.amazon.com", "music"),
+-			"ja" : ("jp", "xml.amazon.co.jp", "music-jp")
++			"en_US" : ("com", "latin1"),
++			"en_GB" : ("co.uk", "latin1"),
++			"de" : ("de", "latin1"),
++			"ja" : ("jp", "utf8")
+ 		}
+ 		self.db = None
+ 		self.entry = None
+@@ -55,10 +57,7 @@ class AmazonCoverArtSearch (object):
+ 				if self._supportedLocales.has_key (lang):
+ 					lc_id = lang
+ 
+-		lc_host = self._supportedLocales[lc_id][1]
+-		lc_name = self._supportedLocales[lc_id][0]
+-		lc_mode = self._supportedLocales[lc_id][2]
+-		return ((lc_host, lc_name, lc_mode))
++		return self._supportedLocales[lc_id]
+ 
+ 	def search (self, db, entry, on_search_completed_callback, *args):
+ 		self.searching = True
+@@ -127,15 +126,17 @@ class AmazonCoverArtSearch (object):
+ 		self.search_next ();
+ 
+ 	def __build_url (self, keyword):
+-		(lc_host, lc_name, lc_mode) = self.__get_locale ()
+-
+-		url = "http://" + lc_host + "/onca/xml3?f=xml"
+-		url += "&t=%s" % ASSOCIATE
+-		url += "&dev-t=%s" % LICENSE_KEY
+-		url += "&type=%s" % 'lite'
+-		url += "&locale=%s" % lc_name
+-		url += "&mode=%s" % lc_mode
+-		url += "&%s=%s" % ('KeywordSearch', urllib.quote (keyword))
++		(tld, encoding) = self.__get_locale ()
++
++		url = "http://ecs.amazonaws." + tld + "/onca/xml" \
++		      "?Service=AWSECommerceService"              \
++		      "&AWSAccessKeyId=" + LICENSE_KEY +          \
++		      "&AssociateTag=" + ASSOCIATE +              \
++		      "&ResponseGroup=Images,ItemAttributes"      \
++		      "&Operation=ItemSearch"                     \
++		      "&SearchIndex=Music"                        \
++		      "&Keywords=" + urllib.quote (keyword.encode (encoding, 'replace'))
++		print "url: '%s'" % (url)
+ 
+ 		return url
+ 
+@@ -161,26 +162,21 @@ class AmazonCoverArtSearch (object):
+ 
+ 	def __unmarshal (self, element):
+ 		rc = Bag ()
+-		if isinstance (element, minidom.Element) and (element.tagName == 'Details'):
+-			rc.URL = element.attributes["url"].value
+ 		childElements = [e for e in element.childNodes if isinstance (e, minidom.Element)]
+ 		if childElements:
+ 			for child in childElements:
+ 				key = child.tagName
+ 				if hasattr (rc, key):
+-					if type (getattr (rc, key)) <> type ([]):
++					if not isinstance (getattr (rc, key), list):
+ 						setattr (rc, key, [getattr (rc, key)])
+-					setattr (rc, key, getattr (rc, key) + [self.__unmarshal (child)])
+-				elif isinstance(child, minidom.Element) and (child.tagName == 'Details'):
+-					setattr (rc,key,[self.__unmarshal(child)])
++					getattr (rc, key).append (self.__unmarshal (child))
++				# get_best_match_urls() wants a list, even if there is only one item/artist
++				elif (child.tagName == "Item") or (child.tagName == "Artist"):
++					setattr (rc, key, [self.__unmarshal(child)])
+ 				else:
+ 					setattr (rc, key, self.__unmarshal(child))
+ 		else:
+ 			rc = "".join ([e.data for e in element.childNodes if isinstance (e, minidom.Text)])
+-			if element.tagName == 'SalesRank':
+-				rc = rc.replace ('.', '')
+-				rc = rc.replace (',', '')
+-				rc = int (rc)
+ 		return rc
+ 
+ 	def on_search_response (self, result_data):
+@@ -194,14 +190,14 @@ class AmazonCoverArtSearch (object):
+ 			self.search_next()
+ 			return
+ 		
+-		data = self.__unmarshal (xmldoc).ProductInfo
++		data = self.__unmarshal (xmldoc).ItemSearchResponse.Items
+ 
+-		if hasattr(data, 'ErrorMsg'):
++		if hasattr (data.Request, "Errors"):
+ 			# Search was unsuccessful, try next keyword
+ 			self.search_next ()
+ 		else:
+ 			# We got some search results
+-			self.on_search_results (data.Details)
++			self.on_search_results (data.Item)
+ 
+ 	def on_search_results (self, results):
+ 		self.on_search_completed (results)
+@@ -224,10 +220,8 @@ class AmazonCoverArtSearch (object):
+ 		return s
+ 
+ 	def __valid_match (self, item):
+-		if item.ImageUrlLarge == "" and item.ImageUrlMedium == "":
+-			print "%s doesn't have image URLs; ignoring" % (item.URL)
+-			return False
+-		return True
++		return (hasattr (item, "LargeImage") or hasattr (item, "MediumImage")) \
++		       and hasattr (item, "ItemAttributes")
+ 
+ 	def get_best_match_urls (self, search_results):
+ 		# Default to "no match", our results must match our criteria
+@@ -238,11 +232,11 @@ class AmazonCoverArtSearch (object):
+ 			if self.search_album != _("Unknown"):
+ 				album_check = self.__tidy_up_string (self.search_album)
+ 				for item in search_results:
++					if not hasattr (item.ItemAttributes, "Title"):
++						continue
+ 
+-					# Check for album name in ProductName
+-					product_name = self.__tidy_up_string (item.ProductName)
+-
+-					if product_name == album_check:
++					album = self.__tidy_up_string (item.ItemAttributes.Title)
++					if album == album_check:
+ 						# Found exact album, can not get better than that
+ 						best_match = item
+ 						break
+@@ -250,8 +244,9 @@ class AmazonCoverArtSearch (object):
+ 					# Check the results for both an album name that contains the name
+ 					# we're searching for, and an album name that's a substring of the
+ 					# name we're searching for
+-					elif (best_match is None) and (product_name.find (album_check) != -1 
+-                                                                  or album_check.find (product_name) != -1):
++					elif (best_match is None) and \
++					     (album.find (album_check) != -1 or
++					      album_check.find (album) != -1):
+ 						best_match = item
+ 
+ 			# If we still have no definite hit, use first result where artist matches
+@@ -261,13 +256,10 @@ class AmazonCoverArtSearch (object):
+ 					# Check if artist appears in the Artists list
+ 					hit = False
+ 					for item in search_results:
++						if not hasattr (item.ItemAttributes, "Artist"):
++							continue
+ 
+-						if type (item.Artists.Artist) <> type ([]):
+-							artists = [item.Artists.Artist]
+-						else:
+-							artists = item.Artists.Artist
+-
+-						for artist in artists:
++						for artist in item.ItemAttributes.Artist:
+ 							artist = self.__tidy_up_string (artist)
+ 							if artist.find (artist_check) != -1:
+ 								best_match = item
+@@ -276,10 +268,9 @@ class AmazonCoverArtSearch (object):
+ 						if hit:
+ 							break
+ 
+-			if best_match:
+-				return filter(lambda x: x != "", [item.ImageUrlLarge, item.ImageUrlMedium])
+-			else:
+-				return []
++			return [getattr (best_match, size).URL for size in ("LargeImage", "MediumImage")
++			        if hasattr (best_match, size)]
+ 
+-		except TypeError:
++		except TypeError, e:
++			print "TypeError: %s" % (e)
+ 			return []
+Amazon: Use batch requests
+---
+ .../artdisplay/artdisplay/AmazonCoverArtSearch.py  |  128 +++++++++++---------
+ 1 files changed, 68 insertions(+), 60 deletions(-)
+
+diff --git a/plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py b/plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py
+index 489d302..13ea35a 100644
+--- a/plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py
++++ b/plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py
+@@ -27,6 +27,10 @@ LICENSE_KEY = "18C3VZN9HCECM5G3HQG2"
+ DEFAULT_LOCALE = "en_US"
+ ASSOCIATE = "webservices-20"
+ 
++# We are not allowed to batch more than 2 requests at once
++# http://docs.amazonwebservices.com/AWSEcommerceService/4-0/PgCombiningOperations.html
++MAX_BATCH_JOBS = 2
++
+ 
+ class Bag: pass
+ 
+@@ -35,29 +39,31 @@ class AmazonCoverArtSearch (object):
+ 		self.searching = False
+ 		self.cancel = False
+ 		self.loader = loader
++		self.db = None
++		self.entry = None
++		(self.tld, self.encoding) = self.__get_locale ()
++
++	def __get_locale (self):
+ 		# "JP is the only locale that correctly takes UTF8 input. All other locales use LATIN1."
+ 		# http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1295&categoryID=117
+-		self._supportedLocales = {
++		supported_locales = {
+ 			"en_US" : ("com", "latin1"),
+ 			"en_GB" : ("co.uk", "latin1"),
+ 			"de" : ("de", "latin1"),
+ 			"ja" : ("jp", "utf8")
+ 		}
+-		self.db = None
+-		self.entry = None
+ 
+-	def __get_locale (self):
+-		default = locale.getdefaultlocale ()
+ 		lc_id = DEFAULT_LOCALE
+-		if default[0] is not None:
+-			if self._supportedLocales.has_key (default[0]):
+-				lc_id = default[0]
++		default = locale.getdefaultlocale ()[0]
++		if default:
++			if supported_locales.has_key (default):
++				lc_id = default
+ 			else:
+-				lang = default[0].split("_")[0]
+-				if self._supportedLocales.has_key (lang):
++				lang = default.split("_")[0]
++				if supported_locales.has_key (lang):
+ 					lc_id = lang
+ 
+-		return self._supportedLocales[lc_id]
++		return supported_locales[lc_id]
+ 
+ 	def search (self, db, entry, on_search_completed_callback, *args):
+ 		self.searching = True
+@@ -71,6 +77,10 @@ class AmazonCoverArtSearch (object):
+ 		st_artist = db.entry_get (entry, rhythmdb.PROP_ARTIST) or _("Unknown")
+ 		st_album = db.entry_get (entry, rhythmdb.PROP_ALBUM) or _("Unknown")
+ 
++		if st_artist == st_album == _("Unknown"):
++			self.on_search_completed (None)
++			return
++
+ 		# Tidy up
+ 
+ 		# Replace quote characters
+@@ -118,60 +128,53 @@ class AmazonCoverArtSearch (object):
+ 				self.keywords.append ("%s %s" % (st_artist, st_album))
+ 				if st_album_no_vol != st_album:
+ 					self.keywords.append ("%s %s" % (st_artist, st_album_no_vol))
+-				if (st_album != _("Unknown")):
+-					self.keywords.append ("Various %s" % (st_album))
++				self.keywords.append ("Various %s" % (st_album))
+ 			self.keywords.append ("%s" % (st_artist))
+ 
+ 		# Initiate asynchronous search
+-		self.search_next ();
++		self.search_next ()
+ 
+-	def __build_url (self, keyword):
+-		(tld, encoding) = self.__get_locale ()
++	def search_next (self):
++		if len (self.keywords) == 0:
++			# No keywords left to search -> no results
++			self.on_search_completed (None)
++			return False
+ 
+-		url = "http://ecs.amazonaws." + tld + "/onca/xml" \
+-		      "?Service=AWSECommerceService"              \
+-		      "&AWSAccessKeyId=" + LICENSE_KEY +          \
+-		      "&AssociateTag=" + ASSOCIATE +              \
+-		      "&ResponseGroup=Images,ItemAttributes"      \
+-		      "&Operation=ItemSearch"                     \
+-		      "&SearchIndex=Music"                        \
+-		      "&Keywords=" + urllib.quote (keyword.encode (encoding, 'replace'))
+-		print "url: '%s'" % (url)
++		self.searching = True
+ 
+-		return url
++		url = "http://ecs.amazonaws." + self.tld + "/onca/xml" \
++		      "?Service=AWSECommerceService"                   \
++		      "&AWSAccessKeyId=" + LICENSE_KEY +               \
++		      "&AssociateTag=" + ASSOCIATE +                   \
++		      "&ResponseGroup=Images,ItemAttributes"           \
++		      "&Operation=ItemSearch"                          \
++		      "&ItemSearch.Shared.SearchIndex=Music"
+ 
+-	def search_next (self):
+-		self.searching = True
+-		
+-		if len (self.keywords)==0:
+-			keyword = None
+-		else:
++		job = 1
++		while job <= MAX_BATCH_JOBS and len (self.keywords) > 0:
+ 			keyword = self.keywords.pop (0)
++			keyword = keyword.encode (self.encoding, "ignore")
++			keyword = keyword.strip ()
++			keyword = urllib.quote (keyword)
++			url += "&ItemSearch.%d.Keywords=%s" % (job, keyword)
++			job += 1
+ 
+-		if keyword is None:
+-			# No keywords left to search -> no results
+-			self.on_search_completed (None)
+-			ret = False
+-		else:
+-			# Retrieve search for keyword
+-			url = self.__build_url (keyword.strip ())
+-			self.loader.get_url (url, self.on_search_response)
+-			ret = True
+-
+-		return ret
++		# Retrieve search for keyword
++		self.loader.get_url (url, self.on_search_response)
++		return True
+ 
+ 	def __unmarshal (self, element):
+ 		rc = Bag ()
+-		childElements = [e for e in element.childNodes if isinstance (e, minidom.Element)]
+-		if childElements:
+-			for child in childElements:
++		child_elements = [e for e in element.childNodes if isinstance (e, minidom.Element)]
++		if child_elements:
++			for child in child_elements:
+ 				key = child.tagName
+ 				if hasattr (rc, key):
+ 					if not isinstance (getattr (rc, key), list):
+ 						setattr (rc, key, [getattr (rc, key)])
+ 					getattr (rc, key).append (self.__unmarshal (child))
+ 				# get_best_match_urls() wants a list, even if there is only one item/artist
+-				elif (child.tagName == "Item") or (child.tagName == "Artist"):
++				elif child.tagName in ("Items", "Item", "Artist"):
+ 					setattr (rc, key, [self.__unmarshal(child)])
+ 				else:
+ 					setattr (rc, key, self.__unmarshal(child))
+@@ -190,14 +193,14 @@ class AmazonCoverArtSearch (object):
+ 			self.search_next()
+ 			return
+ 		
+-		data = self.__unmarshal (xmldoc).ItemSearchResponse.Items
+-
+-		if hasattr (data.Request, "Errors"):
+-			# Search was unsuccessful, try next keyword
++		data = self.__unmarshal (xmldoc)
++		if not hasattr (data, "ItemSearchResponse") or \
++		   not hasattr (data.ItemSearchResponse, "Items"):
++			# Something went wrong ...
+ 			self.search_next ()
+ 		else:
+ 			# We got some search results
+-			self.on_search_results (data.Item)
++			self.on_search_results (data.ItemSearchResponse.Items)
+ 
+ 	def on_search_results (self, results):
+ 		self.on_search_completed (results)
+@@ -227,11 +230,15 @@ class AmazonCoverArtSearch (object):
+ 		# Default to "no match", our results must match our criteria
+ 		best_match = None
+ 
+-		search_results = filter(self.__valid_match, search_results)
+-		try:
++		for result in search_results:
++			if not hasattr (result, "Item"):
++				# Search was unsuccessful, try next batch job
++				continue
++
++			items = filter(self.__valid_match, result.Item)
+ 			if self.search_album != _("Unknown"):
+ 				album_check = self.__tidy_up_string (self.search_album)
+-				for item in search_results:
++				for item in items:
+ 					if not hasattr (item.ItemAttributes, "Title"):
+ 						continue
+ 
+@@ -255,7 +262,7 @@ class AmazonCoverArtSearch (object):
+ 				if best_match is None:
+ 					# Check if artist appears in the Artists list
+ 					hit = False
+-					for item in search_results:
++					for item in items:
+ 						if not hasattr (item.ItemAttributes, "Artist"):
+ 							continue
+ 
+@@ -268,9 +275,10 @@ class AmazonCoverArtSearch (object):
+ 						if hit:
+ 							break
+ 
+-			return [getattr (best_match, size).URL for size in ("LargeImage", "MediumImage")
++			urls = [getattr (best_match, size).URL for size in ("LargeImage", "MediumImage")
+ 			        if hasattr (best_match, size)]
++			if urls:
++				return urls
+ 
+-		except TypeError, e:
+-			print "TypeError: %s" % (e)
+-			return []
++		# No search was successful
++		return []

Modified: extra-x86_64/rhythmbox.install
===================================================================
--- extra-x86_64/rhythmbox.install	2008-06-22 20:52:06 UTC (rev 3479)
+++ extra-x86_64/rhythmbox.install	2008-06-22 20:53:54 UTC (rev 3480)
@@ -5,13 +5,14 @@
   update-desktop-database -q
   gtk-update-icon-cache -f -q -t usr/share/icons/hicolor
   cat << EOF
-==> Rhythmbox support MP3, OGG and APE by default.
-==> To enable additional formats, install these packages:
-==> WMA     : gstreamer0.10-ffmpeg
-==> FLAC    : gstreamer0.10-flac
-==> Musepack: gstreamer0.10-musepack
-==> AAC     : gstreamer0.10-faad
 
+==> Rhythmbox uses GStreamer to play media. By default, only plugins from
+==> gst-plugins-good and gst-plugins-base are installed.
+==>
+==> To play additional media formats, more plugins are available from
+==> gstreamer0.10-ugly-plugins, gstreamer0.10-bad-plugins 
+==> and gstreamer0.10-ffmpeg packages.
+
 EOF
 
 }
@@ -32,8 +33,3 @@
   update-desktop-database -q
   gtk-update-icon-cache -f -q -t usr/share/icons/hicolor
 }
-
-op=$1
-shift
-
-$op $*





More information about the arch-commits mailing list