From d52b9ef1fce4d4db8c948489f009805da6078817 Mon Sep 17 00:00:00 2001
From: Jon Snader <jcs@manfredII.local>
Date: Tue, 9 Dec 2014 13:31:25 -0500
Subject: [PATCH] org.el: Implement sorting Org tables by IP address

* lisp/org.el: Require cl-lib.
  (org-ip-lessp): Implement an IP address comparison function.
  (org-do-sort): Add dispatch to an IP sort. Fix the user prompt
  asking for type of sort.

* lisp/org-table.el (org-table-sort-lines): Added `i' and `I' to the
  description of options in the DOC string.

* doc/org.texi (org-table-sort-lines): Added the capability to sort by
  IP address to the documentation of `org-table-sort-lines'.

These patches implement the capability to sort an Org table by IP address.
---
 doc/org.texi      | 16 ++++++++--------
 lisp/org-table.el | 10 +++++-----
 lisp/org.el       | 25 ++++++++++++++++++++++++-
 3 files changed, 37 insertions(+), 14 deletions(-)

diff --git a/doc/org.texi b/doc/org.texi
index 7b767a3..3b3c085 100644
--- a/doc/org.texi
+++ b/doc/org.texi
@@ -2199,14 +2199,14 @@ below that line.
 @c
 @orgcmd{C-c ^,org-table-sort-lines}
 Sort the table lines in the region.  The position of point indicates the
-column to be used for sorting, and the range of lines is the range
-between the nearest horizontal separator lines, or the entire table.  If
-point is before the first column, you will be prompted for the sorting
-column.  If there is an active region, the mark specifies the first line
-and the sorting column, while point should be in the last line to be
-included into the sorting.  The command prompts for the sorting type
-(alphabetically, numerically, or by time).  When called with a prefix
-argument, alphabetic sorting will be case-sensitive.
+column to be used for sorting, and the range of lines is the range between
+the nearest horizontal separator lines, or the entire table.  If point is
+before the first column, you will be prompted for the sorting column.  If
+there is an active region, the mark specifies the first line and the sorting
+column, while point should be in the last line to be included into the
+sorting.  The command prompts for the sorting type (alphabetically,
+numerically, by time, or by IP address).  When called with a prefix argument,
+alphabetic sorting will be case-sensitive.
 
 @tsubheading{Regions}
 @orgcmd{C-c C-x M-w,org-table-copy-region}
diff --git a/lisp/org-table.el b/lisp/org-table.el
index 8f36d22..7e7fad4 100644
--- a/lisp/org-table.el
+++ b/lisp/org-table.el
@@ -1656,16 +1656,16 @@ specifies the first line and the sorting column, while point
 should be in the last line to be included into the sorting.
 
 The command then prompts for the sorting type which can be
-alphabetically, numerically, or by time (as given in a time stamp
-in the field, or as a HH:MM value).  Sorting in reverse order is
-also possible.
+alphabetically, numerically, by time (as given in a time stamp
+in the field, or as a HH:MM value), or by IP address.  Sorting
+in reverse order is also possible.
 
 With prefix argument WITH-CASE, alphabetic sorting will be case-sensitive.
 
 If SORTING-TYPE is specified when this function is called from a Lisp
 program, no prompting will take place.  SORTING-TYPE must be a character,
-any of (?a ?A ?n ?N ?t ?T) where the capital letter indicate that sorting
-should be done in reverse order."
+any of (?a ?A ?n ?N ?t ?T ?i ?I) where the capital letter indicate that
+sorting should be done in reverse order."
   (interactive "P")
   (let* ((thisline (org-current-line))
 	 (thiscol (org-table-current-column))
diff --git a/lisp/org.el b/lisp/org.el
index bed5cb9..814a229 100755
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -76,6 +76,7 @@
 (require 'calendar)
 (require 'find-func)
 (require 'format-spec)
+(require 'cl-lib)
 
 (or (equal this-command 'eval-buffer)
     (condition-case nil
@@ -9051,6 +9052,23 @@ When sorting is done, call `org-after-sorting-entries-or-items-hook'."
 	(move-marker org-clock-marker (point))))
     (message "Sorting entries...done")))
 
+(defun org-ip-lessp (ip1 ip2 &optional op)
+  "Compare two IP addresses.
+Unless the optional argument OP is provided, this function will return T
+if IP1 is less than IP2 or NIL otherwise. The optional argument OP is
+intended to be #'> to support reverse sorting."
+  (setq cmp (or op #'<))
+  (cl-labels ((compare (l1 l2)
+                       (if (or (null l1) (null l2))
+                           nil
+                         (let ((n1 (string-to-number (car l1)))
+                               (n2 (string-to-number (car l2))))
+                           (cond
+                            ((funcall cmp n1 n2) t)
+                            ((= n1 n2) (compare (cdr l1) (cdr l2)))
+                            (t nil))))))
+    (compare (split-string ip1 "\\.") (split-string ip2 "\\."))))
+
 (defun org-do-sort (table what &optional with-case sorting-type)
   "Sort TABLE of WHAT according to SORTING-TYPE.
 The user will be prompted for the SORTING-TYPE if the call to this
@@ -9061,7 +9079,7 @@ the table.
 If WITH-CASE is non-nil, the sorting will be case-sensitive."
   (unless sorting-type
     (message
-     "Sort %s: [a]lphabetic, [n]umeric, [t]ime.  A/N/T means reversed:"
+     "Sort %s: [a]lphabetic, [n]umeric, [t]ime, [i]p address.  A/N/T/I means reversed:"
      what)
     (setq sorting-type (read-char-exclusive)))
   (let ((dcst (downcase sorting-type))
@@ -9089,6 +9107,11 @@ If WITH-CASE is non-nil, the sorting will be case-sensitive."
 		     (org-hh:mm-string-to-minutes x))
 		    (t 0)))
 	    comparefun (if (= dcst sorting-type) '< '>)))
+     ((= dcst ?i)
+      (setq extractfun (lambda (x) (org-sort-remove-invisible x)))
+      (setq comparefun (if (= dcst sorting-type)
+                           #'org-ip-lessp
+                         (lambda (i1 i2) (org-ip-lessp i1 i2 #'>)))))
      (t (error "Invalid sorting type `%c'" sorting-type)))
 
     (sort (mapcar (lambda (x) (cons (funcall extractfun (car x)) (cdr x)))
-- 
1.9.3 (Apple Git-50)

