diff --git a/.project b/.project new file mode 100644 index 0000000..6695946 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + python-ping + + + + + + org.python.pydev.PyDevBuilder + + + + + + org.python.pydev.pythonNature + + diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..1418452 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,13 @@ + +AUTHORS / CONTRIBUTORS (alphabetic order): + + * Cowles, Matthew Dixon -- ftp://ftp.visi.com/users/mdc/ping.py + * Diemer, Jens -- http://www.jensdiemer.de + * Falatic, Martin -- http://www.falatic.com + * Hallman, Chris -- http://cdhallman.blogspot.com + * Notaras, George -- http://www.g-loaded.eu + * Poincheval, Jerome + * Stauffer, Samuel + * Zach Ware + * zed -- https://github.com/zed + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9cc1267 --- /dev/null +++ b/LICENSE @@ -0,0 +1,11 @@ +The original code derived from ping.c distributed in Linux's netkit. +That code is copyright (c) 1989 by The Regents of the University of California. +That code is in turn derived from code written by Mike Muuss of the +US Army Ballistic Research Laboratory in December, 1983 and +placed in the public domain. They have my thanks. + +Copyright (c) Matthew Dixon Cowles, . +Distributable under the terms of the GNU General Public License +version 2. Provided with no warranties of any sort. + +See AUTHORS for complete list of authors and contributors. \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..244d0be --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +include AUTHORS LICENSE MANIFEST.in README.creole +recursive-include *.py +recursive-exclude * *.pyc +recursive-exclude * *.pyo \ No newline at end of file diff --git a/README b/README deleted file mode 100644 index 1488af0..0000000 --- a/README +++ /dev/null @@ -1,2 +0,0 @@ -Originally from http://svn.pylucid.net/pylucid/CodeSnippets/ping.py -This version maintained at http://github.com/samuel/python-ping \ No newline at end of file diff --git a/README.creole b/README.creole new file mode 100644 index 0000000..3c4a2bf --- /dev/null +++ b/README.creole @@ -0,0 +1,97 @@ +A pure python ping implementation using raw sockets. + +Note that ICMP messages can only be sent from processes running as root +(in Windows, you must run this script as 'Administrator'). + +Original Version from [[ftp://ftp.visi.com/users/mdc/ping.py|Matthew Dixon Cowles]] + +* copyleft 1989-2011 by the python-ping team, see [[https://github.com/jedie/python-ping/blob/master/AUTHORS|AUTHORS]] for more details. +* license: GNU GPL v2, see [[https://github.com/jedie/python-ping/blob/master/LICENSE|LICENSE]] for more details. + + +=== usage === + +{{{ +~/python-ping$ sudo ./ping.py google.com + +PYTHON-PING google.com (74.125.39.147): 55 data bytes +64 bytes from 74.125.39.147: icmp_seq=0 ttl=53 time=23 ms +64 bytes from 74.125.39.147: icmp_seq=1 ttl=52 time=20 ms +64 bytes from 74.125.39.147: icmp_seq=2 ttl=53 time=22 ms + +----74.125.39.147 PYTHON PING Statistics---- +3 packets transmitted, 3 packets received, 0.0% packet loss +round-trip (ms) min/avg/max = 20/22.4/23 +}}} + + +== TODOs == + +* refactor ping.py +* create a CLI interface +* add a "suprocess ping", with output parser + +== Revision history == + +==== Oct. 12, 2011 ==== +Merge sources and create a seperate github repository: +* https://github.com/jedie/python-ping + +Add a simple CLI interface. + +==== September 12, 2011 ==== +Bugfixes + cleanup by Jens Diemer +Tested with Ubuntu + Windows 7 + +==== September 6, 2011 ==== +[[http://www.falatic.com/index.php/39/pinging-with-python|Cleanup by Martin Falatic.]] +Restored lost comments and docs. Improved functionality: constant time between +pings, internal times consistently use milliseconds. Clarified annotations +(e.g., in the checksum routine). Using unsigned data in IP & ICMP header +pack/unpack unless otherwise necessary. Signal handling. Ping-style output +formatting and stats. + +==== August 3, 2011 ==== +Ported to py3k by Zach Ware. Mostly done by 2to3; also minor changes to +deal with bytes vs. string changes (no more ord() in checksum() because +>source_string< is actually bytes, added .encode() to data in +send_one_ping()). That's about it. + +==== March 11, 2010 ==== +changes by Samuel Stauffer: +replaced time.clock with default_timer which is set to +time.clock on windows and time.time on other systems. + +==== November 8, 2009 ==== +Fixes by [[http://www.g-loaded.eu/2009/10/30/python-ping/|George Notaras]], +reported by [[http://cdhallman.blogspot.com|Chris Hallman]]: + +Improved compatibility with GNU/Linux systems. + +Changes in this release: + +Re-use time.time() instead of time.clock(). The 2007 implementation +worked only under Microsoft Windows. Failed on GNU/Linux. +time.clock() behaves differently under [[http://docs.python.org/library/time.html#time.clock|the two OSes]]. + +==== May 30, 2007 ==== +little [[http://www.python-forum.de/post-69122.html#69122|rewrite by Jens Diemer]]: + * change socket asterisk import to a normal import + * replace time.time() with time.clock() + * delete "return None" (or change to "return" only) + * in checksum() rename "str" to "source_string" + +==== December 4, 2000 ==== +Changed the struct.pack() calls to pack the checksum and ID as +unsigned. My thanks to Jerome Poincheval for the fix. + +==== November 22, 1997 ==== +Initial hack. Doesn't do much, but rather than try to guess +what features I (or others) will want in the future, I've only +put in what I need now. + +==== December 16, 1997 ==== +For some reason, the checksum bytes are in the wrong order when +this is run under Solaris 2.X for SPARC but it works right under +Linux x86. Since I don't know just what's wrong, I'll swap the +bytes always and then do an htons(). diff --git a/ping.py b/ping.py old mode 100644 new mode 100755 index af41f1e..739cdf1 --- a/ping.py +++ b/ping.py @@ -1,72 +1,23 @@ #!/usr/bin/env python +# coding: utf-8 """ - A pure python ping implementation using raw socket. + A pure python ping implementation using raw sockets. - - Note that ICMP messages can only be sent from processes running as root. - - - Derived from ping.c distributed in Linux's netkit. That code is - copyright (c) 1989 by The Regents of the University of California. - That code is in turn derived from code written by Mike Muuss of the - US Army Ballistic Research Laboratory in December, 1983 and - placed in the public domain. They have my thanks. + Note that ICMP messages can only be sent from processes running as root + (in Windows, you must run this script as 'Administrator'). Bugs are naturally mine. I'd be glad to hear about them. There are - certainly word - size dependenceies here. - - Copyright (c) Matthew Dixon Cowles, . - Distributable under the terms of the GNU General Public License - version 2. Provided with no warranties of any sort. - - Original Version from Matthew Dixon Cowles: - -> ftp://ftp.visi.com/users/mdc/ping.py - - Rewrite by Jens Diemer: - -> http://www.python-forum.de/post-69122.html#69122 - - - Revision history - ~~~~~~~~~~~~~~~~ - - March 11, 2010 - changes by Samuel Stauffer: - - replaced time.clock with default_timer which is set to - time.clock on windows and time.time on other systems. - - May 30, 2007 - little rewrite by Jens Diemer: - - change socket asterisk import to a normal import - - replace time.time() with time.clock() - - delete "return None" (or change to "return" only) - - in checksum() rename "str" to "source_string" - - November 22, 1997 - Initial hack. Doesn't do much, but rather than try to guess - what features I (or others) will want in the future, I've only - put in what I need now. - - December 16, 1997 - For some reason, the checksum bytes are in the wrong order when - this is run under Solaris 2.X for SPARC but it works right under - Linux x86. Since I don't know just what's wrong, I'll swap the - bytes always and then do an htons(). - - December 4, 2000 - Changed the struct.pack() calls to pack the checksum and ID as - unsigned. My thanks to Jerome Poincheval for the fix. - - - Last commit info: - ~~~~~~~~~~~~~~~~~ - $LastChangedDate: $ - $Rev: $ - $Author: $ + certainly word - size dependencies here. + + :homepage: https://github.com/jedie/python-ping/ + :copyleft: 1989-2011 by the python-ping team, see AUTHORS for more details. + :license: GNU GPL v2, see LICENSE for more details. """ -import os, sys, socket, struct, select, time +import os, sys, socket, struct, select, time, signal + if sys.platform == "win32": # On Windows, the best timer is time.clock() @@ -75,143 +26,298 @@ # On most other platforms the best timer is time.time() default_timer = time.time -# From /usr/include/linux/icmp.h; your milage may vary. -ICMP_ECHO_REQUEST = 8 # Seems to be the same on Solaris. + +# ICMP parameters + +ICMP_ECHOREPLY = 0 # Echo reply (per RFC792) +ICMP_ECHO = 8 # Echo request (per RFC792) +ICMP_MAX_RECV = 2048 # Max size of incoming buffer + +MAX_SLEEP = 1000 + +class MyStats: + thisIP = "0.0.0.0" + pktsSent = 0 + pktsRcvd = 0 + minTime = 999999999 + maxTime = 0 + totTime = 0 + fracLoss = 1.0 + +myStats = MyStats # Used globally def checksum(source_string): """ - I'm not too confident that this is right but testing seems - to suggest that it gives the same answers as in_cksum in ping.c + A port of the functionality of in_cksum() from ping.c + Ideally this would act on the string as a series of 16-bit ints (host + packed), but this works. + Network data is big-endian, hosts are typically little-endian """ + countTo = (int(len(source_string) / 2)) * 2 sum = 0 - countTo = (len(source_string)/2)*2 count = 0 - while count> 16) + (sum & 0xffff) - sum = sum + (sum >> 16) - answer = ~sum - answer = answer & 0xffff + sum &= 0xffffffff # Truncate sum to 32 bits (a variance from ping.c, which + # uses signed ints, but overflow is unlikely in ping) - # Swap bytes. Bugger me if I know why. - answer = answer >> 8 | (answer << 8 & 0xff00) + sum = (sum >> 16) + (sum & 0xffff) # Add high 16 bits to low 16 bits + sum += (sum >> 16) # Add carry from above (if any) + answer = ~sum & 0xffff # Invert and truncate to 16 bits + answer = socket.htons(answer) return answer -def receive_one_ping(my_socket, ID, timeout): +def do_one(destIP, timeout, mySeqNumber, numDataBytes): """ - receive the ping from the socket. + Returns either the delay (in ms) or None on timeout. """ - timeLeft = timeout - while True: - startedSelect = default_timer() - whatReady = select.select([my_socket], [], [], timeLeft) - howLongInSelect = (default_timer() - startedSelect) - if whatReady[0] == []: # Timeout - return + global myStats - timeReceived = default_timer() - recPacket, addr = my_socket.recvfrom(1024) - icmpHeader = recPacket[20:28] - type, code, checksum, packetID, sequence = struct.unpack( - "bbHHh", icmpHeader + delay = None + + try: # One could use UDP here, but it's obscure + mySocket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp")) + except socket.error, (errno, msg): + if errno == 1: + # Operation not permitted - Add more information to traceback + etype, evalue, etb = sys.exc_info() + evalue = etype( + "%s - Note that ICMP messages can only be sent from processes running as root." % evalue + ) + raise etype, evalue, etb + + print("failed. (socket error: '%s')" % msg) + raise # raise the original error + + my_ID = os.getpid() & 0xFFFF + + sentTime = send_one_ping(mySocket, destIP, my_ID, mySeqNumber, numDataBytes) + if sentTime == None: + mySocket.close() + return delay + + myStats.pktsSent += 1; + + recvTime, dataSize, iphSrcIP, icmpSeqNumber, iphTTL = receive_one_ping(mySocket, my_ID, timeout) + + mySocket.close() + + if recvTime: + delay = (recvTime - sentTime) * 1000 + print("%d bytes from %s: icmp_seq=%d ttl=%d time=%d ms" % ( + dataSize, socket.inet_ntoa(struct.pack("!I", iphSrcIP)), icmpSeqNumber, iphTTL, delay) ) - if packetID == ID: - bytesInDouble = struct.calcsize("d") - timeSent = struct.unpack("d", recPacket[28:28 + bytesInDouble])[0] - return timeReceived - timeSent + myStats.pktsRcvd += 1; + myStats.totTime += delay + if myStats.minTime > delay: + myStats.minTime = delay + if myStats.maxTime < delay: + myStats.maxTime = delay + else: + delay = None + print("Request timed out.") - timeLeft = timeLeft - howLongInSelect - if timeLeft <= 0: - return + return delay -def send_one_ping(my_socket, dest_addr, ID): +def send_one_ping(mySocket, destIP, myID, mySeqNumber, numDataBytes): """ - Send one ping to the given >dest_addr<. + Send one ping to the given >destIP<. """ - dest_addr = socket.gethostbyname(dest_addr) + destIP = socket.gethostbyname(destIP) # Header is type (8), code (8), checksum (16), id (16), sequence (16) - my_checksum = 0 + myChecksum = 0 # Make a dummy heder with a 0 checksum. - header = struct.pack("bbHHh", ICMP_ECHO_REQUEST, 0, my_checksum, ID, 1) - bytesInDouble = struct.calcsize("d") - data = (192 - bytesInDouble) * "Q" - data = struct.pack("d", default_timer()) + data + header = struct.pack( + "!BBHHH", ICMP_ECHO, 0, myChecksum, myID, mySeqNumber + ) + + padBytes = [] + startVal = 0x42 + for i in range(startVal, startVal + (numDataBytes)): + padBytes += [(i & 0xff)] # Keep chars in the 0-255 range + data = bytes(padBytes) # Calculate the checksum on the data and the dummy header. - my_checksum = checksum(header + data) + myChecksum = checksum(header + data) # Checksum is in network order # Now that we have the right checksum, we put that in. It's just easier # to make up a new header than to stuff it into the dummy. header = struct.pack( - "bbHHh", ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), ID, 1 + "!BBHHH", ICMP_ECHO, 0, myChecksum, myID, mySeqNumber ) + packet = header + data - my_socket.sendto(packet, (dest_addr, 1)) # Don't know about the 1 + sendTime = default_timer() -def do_one(dest_addr, timeout): + try: + mySocket.sendto(packet, (destIP, 1)) # Port number is irrelevant for ICMP + except socket.error as e: + print("General failure (%s)" % (e.args[1])) + return + + return sendTime + + +def receive_one_ping(mySocket, myID, timeout): """ - Returns either the delay (in seconds) or none on timeout. + Receive the ping from the socket. Timeout = in ms """ - icmp = socket.getprotobyname("icmp") - try: - my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp) - except socket.error, (errno, msg): - if errno == 1: - # Operation not permitted - msg = msg + ( - " - Note that ICMP messages can only be sent from processes" - " running as root." - ) - raise socket.error(msg) - raise # raise the original error + timeLeft = timeout / 1000 - my_ID = os.getpid() & 0xFFFF + while True: # Loop while waiting for packet or timeout + startedSelect = default_timer() + whatReady = select.select([mySocket], [], [], timeLeft) + howLongInSelect = (default_timer() - startedSelect) + if whatReady[0] == []: # Timeout + return None, 0, 0, 0, 0 - send_one_ping(my_socket, dest_addr, my_ID) - delay = receive_one_ping(my_socket, my_ID, timeout) + timeReceived = default_timer() + + recPacket, addr = mySocket.recvfrom(ICMP_MAX_RECV) + + ipHeader = recPacket[:20] + iphVersion, iphTypeOfSvc, iphLength, \ + iphID, iphFlags, iphTTL, iphProtocol, \ + iphChecksum, iphSrcIP, iphDestIP = struct.unpack( + "!BBHHHBBHII", ipHeader + ) + + icmpHeader = recPacket[20:28] + icmpType, icmpCode, icmpChecksum, \ + icmpPacketID, icmpSeqNumber = struct.unpack( + "!BBHHH", icmpHeader + ) + + if icmpPacketID == myID: # Our packet + dataSize = len(recPacket) - 28 + return timeReceived, dataSize, iphSrcIP, icmpSeqNumber, iphTTL + + timeLeft = timeLeft - howLongInSelect + if timeLeft <= 0: + return None, 0, 0, 0, 0 - my_socket.close() - return delay + +def dump_stats(): + """ + Show stats when pings are done + """ + global myStats + + print("\n----%s PYTHON PING Statistics----" % (myStats.thisIP)) + + if myStats.pktsSent > 0: + myStats.fracLoss = (myStats.pktsSent - myStats.pktsRcvd) / myStats.pktsSent + + print("%d packets transmitted, %d packets received, %0.1f%% packet loss" % ( + myStats.pktsSent, myStats.pktsRcvd, 100.0 * myStats.fracLoss + )) + + if myStats.pktsRcvd > 0: + print("round-trip (ms) min/avg/max = %d/%0.1f/%d" % ( + myStats.minTime, myStats.totTime / myStats.pktsRcvd, myStats.maxTime + )) + + print() + return + + +def signal_handler(signum, frame): + """ + Handle exit via signals + """ + dump_stats() + print("\n(Terminated with signal %d)\n" % (signum)) + sys.exit(0) -def verbose_ping(dest_addr, timeout = 2, count = 4): +def verbose_ping(hostname, timeout=1000, count=3, numDataBytes=55): """ - Send >count< ping to >dest_addr< with the given >timeout< and display + Send >count< ping to >destIP< with the given >timeout< and display the result. """ - for i in xrange(count): - print "ping %s..." % dest_addr, - try: - delay = do_one(dest_addr, timeout) - except socket.gaierror, e: - print "failed. (socket error: '%s')" % e[1] - break - - if delay == None: - print "failed. (timeout within %ssec.)" % timeout - else: - delay = delay * 1000 - print "get ping in %0.4fms" % delay - print + global myStats + + signal.signal(signal.SIGINT, signal_handler) # Handle Ctrl-C + if hasattr(signal, "SIGBREAK"): + # Handle Ctrl-Break e.g. under Windows + signal.signal(signal.SIGBREAK, signal_handler) + + myStats = MyStats() # Reset the stats + + mySeqNumber = 0 # Starting value + + try: + destIP = socket.gethostbyname(hostname) + print("\nPYTHON-PING %s (%s): %d data bytes" % (hostname, destIP, numDataBytes)) + except socket.gaierror as e: + print("\nPYTHON-PING: Unknown host: %s (%s)" % (hostname, e.args[1])) + print() + return + + myStats.thisIP = destIP + + for i in range(count): + delay = do_one(destIP, timeout, mySeqNumber, numDataBytes) + + if delay == None: + delay = 0 + + mySeqNumber += 1 + + # Pause for the remainder of the MAX_SLEEP period (if applicable) + if (MAX_SLEEP > delay): + time.sleep((MAX_SLEEP - delay) / 1000) + + dump_stats() if __name__ == '__main__': - verbose_ping("heise.de") - verbose_ping("google.com") - verbose_ping("a-test-url-taht-is-not-available.com") - verbose_ping("192.168.1.1") + # FIXME: Add a real CLI + if len(sys.argv) == 0: + print "DEMO" + + # These should work: + verbose_ping("heise.de") + verbose_ping("google.com") + + # Inconsistent on Windows w/ ActivePython (Python 3.2 resolves correctly + # to the local host, but 2.7 tries to resolve to the local *gateway*) + verbose_ping("localhost") + + # Should fail with 'getaddrinfo failed': + verbose_ping("foobar_url.foobar") + + # Should fail (timeout), but it depends on the local network: + verbose_ping("192.168.255.254") + + # Should fails with 'The requested address is not valid in its context': + verbose_ping("0.0.0.0") + elif len(sys.argv) == 2: + verbose_ping(sys.argv[1]) + else: + print "Error: call ./ping.py domain.tld" diff --git a/ping_header_info.txt b/ping_header_info.txt new file mode 100644 index 0000000..371c729 --- /dev/null +++ b/ping_header_info.txt @@ -0,0 +1,50 @@ +IP header info from RFC791 + -> http://tools.ietf.org/html/rfc791) + +0 1 2 3 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|Version| IHL |Type of Service| Total Length | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Identification |Flags| Fragment Offset | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Time to Live | Protocol | Header Checksum | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Source Address | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Destination Address | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Options | Padding | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +=========================================================================== +ICMP Echo / Echo Reply Message header info from RFC792 + -> http://tools.ietf.org/html/rfc792 + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Type | Code | Checksum | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Identifier | Sequence Number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Data ... + +-+-+-+-+- + +=========================================================================== +ICMP parameter info: + -> http://www.iana.org/assignments/icmp-parameters/icmp-parameters.xml + +=========================================================================== +An example of ping's typical output: + +PING heise.de (193.99.144.80): 56 data bytes +64 bytes from 193.99.144.80: icmp_seq=0 ttl=240 time=127 ms +64 bytes from 193.99.144.80: icmp_seq=1 ttl=240 time=127 ms +64 bytes from 193.99.144.80: icmp_seq=2 ttl=240 time=126 ms +64 bytes from 193.99.144.80: icmp_seq=3 ttl=240 time=126 ms +64 bytes from 193.99.144.80: icmp_seq=4 ttl=240 time=127 ms + +----heise.de PING Statistics---- +5 packets transmitted, 5 packets received, 0.0% packet loss +round-trip (ms) min/avg/max/med = 126/127/127/127 \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..8dba88e --- /dev/null +++ b/setup.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" + distutils setup + ~~~~~~~~~~~~~~~ + + :homepage: https://github.com/jedie/python-ping/ + :copyleft: 1989-2011 by the python-ping team, see AUTHORS for more details. + :license: GNU GPL v2, see LICENSE for more details. +""" + +import os +import subprocess +import sys +import time +import warnings + +from setuptools import setup, find_packages, Command + +PACKAGE_ROOT = os.path.dirname(os.path.abspath(__file__)) + + +#VERBOSE = True +VERBOSE = False + +def _error(msg): + if VERBOSE: + warnings.warn(msg) + return "" + +def get_version_from_git(): + try: + process = subprocess.Popen( + # %ct: committer date, UNIX timestamp + ["/usr/bin/git", "log", "--pretty=format:%ct-%h", "-1", "HEAD"], + shell=False, cwd=PACKAGE_ROOT, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + ) + except Exception, err: + return _error("Can't get git hash: %s" % err) + + process.wait() + returncode = process.returncode + if returncode != 0: + return _error( + "Can't get git hash, returncode was: %r" + " - git stdout: %r" + " - git stderr: %r" + % (returncode, process.stdout.readline(), process.stderr.readline()) + ) + + output = process.stdout.readline().strip() + try: + raw_timestamp, hash = output.split("-", 1) + timestamp = int(raw_timestamp) + except Exception, err: + return _error("Error in git log output! Output was: %r" % output) + + try: + timestamp_formatted = time.strftime("%Y.%m.%d", time.gmtime(timestamp)) + except Exception, err: + return _error("can't convert %r to time string: %s" % (timestamp, err)) + + return "%s.%s" % (timestamp_formatted, hash) + + +# convert creole to ReSt on-the-fly, see also: +# https://code.google.com/p/python-creole/wiki/UseInSetup +try: + from creole.setup_utils import get_long_description +except ImportError: + if "register" in sys.argv or "sdist" in sys.argv or "--long-description" in sys.argv: + etype, evalue, etb = sys.exc_info() + evalue = etype("%s - Please install python-creole >= v0.8 - e.g.: pip install python-creole" % evalue) + raise etype, evalue, etb + long_description = None +else: + long_description = get_long_description(PACKAGE_ROOT) + + +def get_authors(): + authors = [] + try: + f = file(os.path.join(PACKAGE_ROOT, "AUTHORS"), "r") + for line in f: + if not line.strip().startswith("*"): + continue + if "--" in line: + line = line.split("--", 1)[0] + authors.append(line.strip(" *\r\n")) + f.close() + authors.sort() + except Exception, err: + authors = "[Error: %s]" % err + return authors + + +setup( + name='python-ping', + version=get_version_from_git(), + description='A pure python ICMP ping implementation using raw sockets.', + long_description=long_description, + author=get_authors(), + maintainer="Jens Diemer", + maintainer_email="python-ping@jensdiemer.de", + url='https://github.com/jedie/python-ping/', + packages=find_packages(), + include_package_data=True, # include package data under svn source control + zip_safe=False, + classifiers=[ + # http://pypi.python.org/pypi?%3Aaction=list_classifiers +# "Development Status :: 4 - Beta", + "Development Status :: 5 - Production/Stable", + "Environment :: Web Environment", + "Intended Audience :: Developers", + "Intended Audience :: Education", + "Intended Audience :: End Users/Desktop", + "License :: OSI Approved :: GNU General Public License (GPL)", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Topic :: Internet", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: System :: Networking :: Monitoring", + ], +)