Skip to content

feat(drive): add tree command #66

@rianjs

Description

@rianjs

Summary

Add gro drive tree command to display the folder structure of Google Drive in a tree format.

Usage

gro drive tree                    # Show folder tree from root
gro drive tree <folder-id>        # Show tree from specific folder
gro drive tree --depth 3          # Limit depth
gro drive tree --files            # Include files, not just folders
gro drive tree --json             # JSON output

Flags

Flag Short Default Description
--depth -d 2 Maximum depth to traverse
--files - false Include files in addition to folders
--json -j false Output as JSON

Implementation

Files to Create/Modify

  • internal/cmd/drive/tree.go - Tree command implementation
  • internal/cmd/drive/tree_test.go - Unit tests
  • internal/cmd/drive/drive.go - Register tree subcommand

Data Structure

type TreeNode struct {
    ID       string      `json:"id"`
    Name     string      `json:"name"`
    Type     string      `json:"type"`  // "folder" or file type
    Children []*TreeNode `json:"children,omitempty"`
}

Algorithm

func buildTree(folderID string, depth int, includeFiles bool) (*TreeNode, error) {
    if depth <= 0 {
        return nil, nil
    }
    
    // Get folder info
    folder, err := client.GetFile(folderID)
    
    // List children
    query := fmt.Sprintf("'%s' in parents and trashed = false", folderID)
    if !includeFiles {
        query += " and mimeType = 'application/vnd.google-apps.folder'"
    }
    children, err := client.ListFiles(query, 100)
    
    node := &TreeNode{
        ID:   folder.ID,
        Name: folder.Name,
        Type: "folder",
    }
    
    // Recursively build children
    for _, child := range children {
        if child.MimeType == folderMimeType {
            childNode, _ := buildTree(child.ID, depth-1, includeFiles)
            node.Children = append(node.Children, childNode)
        } else {
            node.Children = append(node.Children, &TreeNode{
                ID:   child.ID,
                Name: child.Name,
                Type: getTypeName(child.MimeType),
            })
        }
    }
    
    return node, nil
}

Text Output Format

My Drive
├── Projects
│   ├── Q4 Planning
│   │   └── Budget.xlsx
│   └── Marketing
│       ├── Campaigns
│       └── Assets
├── Documents
│   ├── Reports
│   └── Templates
└── Shared with me
    └── Team Files

Without --files:

My Drive
├── Projects
│   ├── Q4 Planning
│   └── Marketing
│       ├── Campaigns
│       └── Assets
├── Documents
│   ├── Reports
│   └── Templates
└── Shared with me
    └── Team Files

JSON Output

{
  "id": "root",
  "name": "My Drive",
  "type": "folder",
  "children": [
    {
      "id": "1abc...",
      "name": "Projects",
      "type": "folder",
      "children": [
        {
          "id": "2def...",
          "name": "Q4 Planning",
          "type": "folder",
          "children": [
            {
              "id": "3ghi...",
              "name": "Budget.xlsx",
              "type": "spreadsheet"
            }
          ]
        }
      ]
    }
  ]
}

Performance Considerations

  • Default depth of 2 to avoid excessive API calls
  • Consider pagination for folders with many children
  • Could add --max-children flag to limit children per folder

Acceptance Criteria

  • Shows folder structure from root by default
  • Shows tree from specific folder when folder-id provided
  • Respects depth limit
  • --files flag includes files in output
  • Tree characters render correctly (├── └── │)
  • JSON output works
  • Handles empty folders gracefully
  • make verify passes

Blocked By

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions