Repository: pydap/pydap

Issue:
add tree method to DAP to represent dataset structure
<!-- Thank you very much for your input! -->

### Description
I think it would make sense to add a little function to interactively on a notebook or ipython, quickly represent the dataset structure without having to go to a DAP response form.

For example:

```python
from pydap.tests.datasets import SimpleGroup as Dataset
Dataset.tree()
```
and have it return
```python
.example dataset
└──SimpleGroup
   ├──Temperature
   ├──Salinity
   ├──X
   └──Y
```


Discussion:
Mikejmnez: Awesome!


Patch:
diff --git a/src/pydap/lib.py b/src/pydap/lib.py
index 6ca8de9..589dbfb 100644
--- a/src/pydap/lib.py
+++ b/src/pydap/lib.py
@@ -296,6 +296,31 @@ def walk(var, type=object):
             yield var
 
 
+def tree(template, prefix=""):
+    # Print the current node's name, add '.' at the root level
+    if prefix == "":
+        print(f".{unquote_(template.name)}")
+    else:
+        print(template.name)
+
+    # Iterate over the children
+    Nchild = len([child for child in template.children()])
+    for i, child in enumerate(template.children()):
+        # Determine the prefix for the current child
+        if i == Nchild - 1:
+            child_prefix = "└──"
+            next_prefix = "   "
+        else:
+            child_prefix = "├──"
+            next_prefix = "│  "
+
+        # Print the current child
+        print(prefix + child_prefix, end="")
+
+        # Recursively call tree on the child with updated prefix
+        tree(child, prefix + next_prefix)
+
+
 def fix_shorthand(projection, dataset):
     """Fix shorthand notation in the projection.
 
diff --git a/src/pydap/model.py b/src/pydap/model.py
index e252df2..efce238 100644
--- a/src/pydap/model.py
+++ b/src/pydap/model.py
@@ -176,7 +176,7 @@ from functools import reduce
 
 import numpy as np
 
-from .lib import _quote, decode_np_strings
+from .lib import _quote, decode_np_strings, tree
 
 __all__ = [
     "BaseType",
@@ -453,6 +453,9 @@ class StructureType(DapType, Mapping):
         for key in self._visible_keys:
             yield self[key]
 
+    def tree(self):
+        return tree(self)
+
     def __setitem__(self, key, item):
         key = _quote(key)
         if key != item.name:


Test patch:
diff --git a/src/pydap/tests/test_lib.py b/src/pydap/tests/test_lib.py
index 8575f6e..c205993 100644
--- a/src/pydap/tests/test_lib.py
+++ b/src/pydap/tests/test_lib.py
@@ -14,9 +14,10 @@ from pydap.lib import (
     fix_slice,
     get_var,
     hyperslab,
+    tree,
     walk,
 )
-from pydap.model import BaseType, DatasetType, SequenceType, StructureType
+from pydap.model import BaseType, DatasetType, GroupType, SequenceType, StructureType
 
 
 class TestQuote(unittest.TestCase):
@@ -233,6 +234,24 @@ class TestWalk(unittest.TestCase):
         self.assertEqual(list(walk(self.dataset, SequenceType)), [self.dataset.d])
 
 
+class TestTree(unittest.TestCase):
+    """Test the ``tree`` func"""
+
+    def setUp(self):
+        self.dataset = DatasetType("name")
+        self.dataset["a"] = BaseType("a")
+        self.dataset["Group1"] = GroupType("Group1")
+        self.dataset["/Group1/Seq1"] = SequenceType("Seq1")
+        self.dataset["/Group1/Seq1/b"] = BaseType("b")
+        self.dataset["/Group1/Seq1/c"] = BaseType("c")
+        self.dataset["d"] = StructureType("d")
+        self.dataset["e"] = SequenceType("e")
+
+    def test_tree_repr(self):
+        self.assertEqual(self.dataset.tree(), tree(self.dataset))
+        self.assertEqual(self.dataset["Group1"].tree(), tree(self.dataset["Group1"]))
+
+
 class TestFixShorthand(unittest.TestCase):
     """Test the ``fix_shorthand`` function."""