Repository: saulpw/visidata

Issue:
Type as: IPv4
It would be helpful to be able to cast values as an IPv4 datatype, and then allow filtering of that data in CIDR notation. Alternatively if someone has a suggestion on how to achieve this currently, I would be very grateful!

Thank you kindly for developing such a fantastic tool.


Discussion:
Hi @ssnkhan, thanks for the request.  If you can share the Python methods you currently use to parse and filter IPv4 addresses, maybe some sample code, we could probably pull something together in short order.

Kondo'ed for now.
I've got some snippets in my `~/.visidatarc` that may be relevant here:

```python
import ipaddress

TableSheet.addCommand(
    None,
    "type-ipaddr",
    "cursorCol.type=ipaddress.ip_address",
    "set type of current column to IP address",
)
TableSheet.addCommand(
    None,
    "type-ipnet",
    "cursorCol.type=ipaddress.ip_network",
    "set type of current column to IP network",
)

@Column.api
def select_supernets(col, addr):
    sheet = col.sheet
    addr = ipaddress.ip_network(addr.strip())
    vd.status(f'selecting rows where {col.name} is a supernet of "{str(addr)}"')
    sheet.select(
        [
            row
            for row in sheet.rows
            if addr.subnet_of(ipaddress.ip_network(col.getValue(row).strip()))
        ]
    )

BaseSheet.addCommand(
    "",
    "select-supernets",
    'cursorCol.select_supernets(inputExpr("ip or cidr block: "))',
    "select rows where the CIDR block contains a provided IP address or network",
)
```

I wouldn't guess that _all_ of this makes sense out of the box, but maybe at least the type conversions do? I mostly used `select-supernets` when I was looking at a sheet full of CIDR blocks and trying to quickly find which ones contained an IP address I cared about. Which sounds like it _might_ be in the neighborhood of what @ssnkhan is trying to do.
@ajkerrigan This is good stuff, can we put this in `visidata/features/type_ipaddr.py`?  I think the only thing I would change is to move the command to `TableSheet` instead (since it depends on cursorCol, which BaseSheet doesn't necessary have).

Resolving patch:
diff --git a/visidata/features/type_ipaddr.py b/visidata/features/type_ipaddr.py
new file mode 100644
--- /dev/null
+++ b/visidata/features/type_ipaddr.py
@@ -0,0 +1,69 @@
+"""
+Column types and utility commands related to IP addresses.
+"""
+import ipaddress
+
+from visidata import vd
+from visidata.sheets import Column, TableSheet
+
+
+def isSupernet(cell, network, isNull):
+    """Is `cell` a supernet of `network`?
+
+    Treat nulls as false, and perform conversions to IP network objects only
+    if necessary.
+    """
+    if isNull(cell):
+        return False
+    if not isinstance(cell, ipaddress._BaseNetwork):
+        try:
+            cell = ipaddress.ip_network(str(cell).strip())
+        except ValueError:
+            return False
+    return cell.supernet_of(network)
+
+
+@Column.api
+def selectSupernets(col, ip):
+    """Select rows based on network containment
+
+    Given an IP address (e.g. 10.0.0.0) or network (e.g. 10.0.0.0/8) as input,
+    select rows whose network address space completely contains the input network.
+    """
+    if not ip:
+        return
+
+    sheet = col.sheet
+    network = ipaddress.ip_network(ip.strip())
+    isNull = sheet.isNullFunc()
+
+    vd.status(f'selecting rows where {col.name} is a supernet of "{str(network)}"')
+    sheet.select(
+        [
+            row
+            for row in sheet.rows
+            if isSupernet(col.getTypedValue(row), network, isNull)
+        ]
+    )
+
+
+TableSheet.addCommand(
+    None,
+    "type-ipaddr",
+    "cursorCol.type=ipaddress.ip_address",
+    "set type of current column to IP address",
+)
+TableSheet.addCommand(
+    None,
+    "type-ipnet",
+    "cursorCol.type=ipaddress.ip_network",
+    "set type of current column to IP network",
+)
+TableSheet.addCommand(
+    None,
+    "select-supernets",
+    'cursorCol.selectSupernets(input("ip or cidr block: "))',
+    "select rows where the CIDR block value includes the input address space",
+)
+
+vd.addGlobals({"ipaddress": ipaddress})


Test patch: