1
+ from typing import Callable
2
+
1
3
from dvc .cli import completion , formatter
2
4
from dvc .cli .command import CmdBaseNoRepo
3
5
from dvc .cli .utils import DictAction , append_doc_link
9
11
logger = logger .getChild (__name__ )
10
12
11
13
12
- def _format_entry (entry , fmt , with_size = True , with_md5 = False ):
14
+ def _get_formatter (with_color : bool = False ) -> Callable [[dict ], str ]:
15
+ def fmt (entry : dict ) -> str :
16
+ return entry ["path" ]
17
+
18
+ if with_color :
19
+ ls_colors = LsColors ()
20
+ return ls_colors .format
21
+
22
+ return fmt
23
+
24
+
25
+ def _format_entry (entry , name , with_size = True , with_hash = False ):
13
26
from dvc .utils .humanize import naturalsize
14
27
15
28
ret = []
@@ -20,60 +33,156 @@ def _format_entry(entry, fmt, with_size=True, with_md5=False):
20
33
else :
21
34
size = naturalsize (size )
22
35
ret .append (size )
23
- if with_md5 :
36
+ if with_hash :
24
37
md5 = entry .get ("md5" , "" )
25
38
ret .append (md5 )
26
- ret .append (fmt ( entry ) )
39
+ ret .append (name )
27
40
return ret
28
41
29
42
30
- def show_entries (entries , with_color = False , with_size = False , with_md5 = False ):
31
- if with_color :
32
- ls_colors = LsColors ()
33
- fmt = ls_colors .format
34
- else :
35
-
36
- def fmt (entry ):
37
- return entry ["path" ]
38
-
39
- if with_size or with_md5 :
43
+ def show_entries (entries , with_color = False , with_size = False , with_hash = False ):
44
+ fmt = _get_formatter (with_color )
45
+ if with_size or with_hash :
46
+ colalign = ("right" ,) if with_size else None
40
47
ui .table (
41
48
[
42
- _format_entry (entry , fmt , with_size = with_size , with_md5 = with_md5 )
49
+ _format_entry (
50
+ entry ,
51
+ fmt (entry ),
52
+ with_size = with_size ,
53
+ with_hash = with_hash ,
54
+ )
43
55
for entry in entries
44
- ]
56
+ ],
57
+ colalign = colalign ,
45
58
)
46
59
return
47
60
48
61
# NOTE: this is faster than ui.table for very large number of entries
49
62
ui .write ("\n " .join (fmt (entry ) for entry in entries ))
50
63
51
64
65
+ class TreePart :
66
+ Edge = "├── "
67
+ Line = "│ "
68
+ Corner = "└── "
69
+ Blank = " "
70
+
71
+
72
+ def _build_tree_structure (
73
+ entries , with_color = False , with_size = False , with_hash = False , _depth = 0 , _prefix = ""
74
+ ):
75
+ rows = []
76
+ fmt = _get_formatter (with_color )
77
+
78
+ num_entries = len (entries )
79
+ for i , (name , entry ) in enumerate (entries .items ()):
80
+ entry ["path" ] = name
81
+ is_last = i >= num_entries - 1
82
+ tree_part = ""
83
+ if _depth > 0 :
84
+ tree_part = TreePart .Corner if is_last else TreePart .Edge
85
+
86
+ row = _format_entry (
87
+ entry ,
88
+ _prefix + tree_part + fmt (entry ),
89
+ with_size = with_size ,
90
+ with_hash = with_hash ,
91
+ )
92
+ rows .append (row )
93
+
94
+ if contents := entry .get ("contents" ):
95
+ new_prefix = _prefix
96
+ if _depth > 0 :
97
+ new_prefix += TreePart .Blank if is_last else TreePart .Line
98
+ new_rows = _build_tree_structure (
99
+ contents ,
100
+ with_color = with_color ,
101
+ with_size = with_size ,
102
+ with_hash = with_hash ,
103
+ _depth = _depth + 1 ,
104
+ _prefix = new_prefix ,
105
+ )
106
+ rows .extend (new_rows )
107
+
108
+ return rows
109
+
110
+
111
+ def show_tree (entries , with_color = False , with_size = False , with_hash = False ):
112
+ import tabulate
113
+
114
+ rows = _build_tree_structure (
115
+ entries ,
116
+ with_color = with_color ,
117
+ with_size = with_size ,
118
+ with_hash = with_hash ,
119
+ )
120
+
121
+ colalign = ("right" ,) if with_size else None
122
+
123
+ _orig = tabulate .PRESERVE_WHITESPACE
124
+ tabulate .PRESERVE_WHITESPACE = True
125
+ try :
126
+ ui .table (rows , colalign = colalign )
127
+ finally :
128
+ tabulate .PRESERVE_WHITESPACE = _orig
129
+
130
+
52
131
class CmdList (CmdBaseNoRepo ):
53
- def run (self ):
132
+ def _show_tree (self ):
133
+ from dvc .repo .ls import ls_tree
134
+
135
+ entries = ls_tree (
136
+ self .args .url ,
137
+ self .args .path ,
138
+ rev = self .args .rev ,
139
+ dvc_only = self .args .dvc_only ,
140
+ config = self .args .config ,
141
+ remote = self .args .remote ,
142
+ remote_config = self .args .remote_config ,
143
+ maxdepth = self .args .level ,
144
+ )
145
+ show_tree (
146
+ entries ,
147
+ with_color = True ,
148
+ with_size = self .args .size ,
149
+ with_hash = self .args .show_hash ,
150
+ )
151
+ return 0
152
+
153
+ def _show_list (self ):
54
154
from dvc .repo import Repo
55
155
56
- try :
57
- entries = Repo .ls (
58
- self .args .url ,
59
- self .args .path ,
60
- rev = self .args .rev ,
61
- recursive = self .args .recursive ,
62
- dvc_only = self .args .dvc_only ,
63
- config = self .args .config ,
64
- remote = self .args .remote ,
65
- remote_config = self .args .remote_config ,
156
+ entries = Repo .ls (
157
+ self .args .url ,
158
+ self .args .path ,
159
+ rev = self .args .rev ,
160
+ recursive = self .args .recursive ,
161
+ dvc_only = self .args .dvc_only ,
162
+ config = self .args .config ,
163
+ remote = self .args .remote ,
164
+ remote_config = self .args .remote_config ,
165
+ maxdepth = self .args .level ,
166
+ )
167
+ if self .args .json :
168
+ ui .write_json (entries )
169
+ elif entries :
170
+ show_entries (
171
+ entries ,
172
+ with_color = True ,
173
+ with_size = self .args .size ,
174
+ with_hash = self .args .show_hash ,
66
175
)
67
- if self . args . json :
68
- ui . write_json ( entries )
69
- elif entries :
70
- show_entries (
71
- entries ,
72
- with_color = True ,
73
- with_size = self . args . size ,
74
- with_md5 = self .args .show_hash ,
75
- )
76
- return 0
176
+ return 0
177
+
178
+ def run ( self ) :
179
+ if self . args . tree and self . args . json :
180
+ raise DvcException ( "Cannot use --tree and --json options together." )
181
+
182
+ try :
183
+ if self .args .tree :
184
+ return self . _show_tree ( )
185
+ return self . _show_list ()
77
186
except FileNotFoundError :
78
187
logger .exception ("" )
79
188
return 1
@@ -102,6 +211,19 @@ def add_parser(subparsers, parent_parser):
102
211
action = "store_true" ,
103
212
help = "Recursively list files." ,
104
213
)
214
+ list_parser .add_argument (
215
+ "-T" ,
216
+ "--tree" ,
217
+ action = "store_true" ,
218
+ help = "Recurse into directories as a tree." ,
219
+ )
220
+ list_parser .add_argument (
221
+ "-L" ,
222
+ "--level" ,
223
+ metavar = "depth" ,
224
+ type = int ,
225
+ help = "Limit the depth of recursion." ,
226
+ )
105
227
list_parser .add_argument (
106
228
"--dvc-only" , action = "store_true" , help = "Show only DVC outputs."
107
229
)
0 commit comments