dirtree 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e2547ddc3ecb80bf7083207cbca639f543c304b6
4
+ data.tar.gz: 12d42cd27343ad8eebaa262ca9b82181a6bb71fd
5
+ SHA512:
6
+ metadata.gz: 494cc0241accc14941175013b3c7a493d71b79a88a3b1ce87a5fd634477d97cc926bd8c079d74ae0cb9bcc6dadedbe22568fabae9e8c1752df25feff3dc833a5
7
+ data.tar.gz: 229f45fdfaf9860d6554393501f9c4c6a318bee3f1bcc2f593c4b43d322fa532edaf21e7abccd63b2a3a5b60402f1882bf2df6cb4ebc2f39df63f34793af71a6
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in dirtree.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Emad Elsaid
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,47 @@
1
+ # Dirtree
2
+
3
+ Dirtree visualizes an list of file paths into a tree graph, printed as HTML page, it can be useful in visualizing a whole project you're working on to start cleanup or organizing your code or spotting large directories or unneeded files.
4
+
5
+ ## Installation
6
+
7
+
8
+ $ gem install dirtree
9
+
10
+ ## Usage
11
+
12
+ Usage: dirtree [options]... [file]...
13
+ -v, --version Print version
14
+ -h, --help Show this help text
15
+ -o, --output=File.html Specify a path to write output, if not specified output will be printed to STDOUT
16
+
17
+
18
+ ### Visualize current directory recursively
19
+
20
+ $ dirtree -o output.html **/* *
21
+
22
+ ### Visualize files from git ls
23
+
24
+ $ git ls-files | dirtree -o output.html
25
+
26
+ ### Dirtree prints to standard output if no --output option specified so you can redirect it
27
+
28
+ $ git ls-files | dirtree > output.html
29
+
30
+ ### visualize only files that include specific word
31
+
32
+ $ git ls-files | grep keyword | dirtree > output.html
33
+
34
+ ### works with find
35
+
36
+ visulaize all files that ends with `rb`
37
+
38
+ $ find ~ -name *rb | dirtree > output.html
39
+
40
+
41
+ ## Contributing
42
+
43
+ Bug reports and pull requests are welcome on GitHub at https://github.com/blazeeboy/dirtree.
44
+
45
+ ## License
46
+
47
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'dirtree/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'dirtree'
8
+ spec.version = Dirtree::VERSION
9
+ spec.authors = ['Emad Elsaid']
10
+ spec.email = ['blazeeboy@gmail.com']
11
+
12
+ spec.summary = 'display list of file paths as an interactive tree'
13
+ spec.description = 'display list of file paths as an interactive tree'
14
+ spec.homepage = 'https://www.github.com/blazeeboy/dirtree'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+
21
+ spec.bindir = 'exe'
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.add_development_dependency 'bundler', '~> 1.15'
26
+ spec.add_development_dependency 'rake', '~> 10.0'
27
+ end
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+ require 'dirtree'
3
+ require 'optparse'
4
+ require 'json'
5
+ require 'erb'
6
+
7
+ options = {}
8
+ OptionParser.new do |opts|
9
+ opts.banner = 'Usage: dirtree [options]... [file]...'
10
+
11
+ opts.on('-v', '--version', 'Print version') do
12
+ puts "Dirtree version #{Dirtree::VERSION}"
13
+ exit
14
+ end
15
+
16
+ opts.on('-h', '--help', 'Show this help text') do
17
+ puts opts
18
+ exit
19
+ end
20
+
21
+ opts.on('-oFile.html', '--output=File.html', 'Specify a path to write output, if not specified output will be printed to STDOUT') do |value|
22
+ options[:output] = value
23
+ end
24
+ end.parse!
25
+
26
+ files = ARGV.empty? ? STDIN.read.lines : ARGV
27
+ files.map!(&:strip)
28
+
29
+ root = Dirtree::Node.new('/')
30
+ files.each { |file| root.insert(file.split('/')) }
31
+ templates_dir = File.join(File.dirname(__FILE__), '..', 'templates')
32
+ template_file = File.join(templates_dir, 'tree.html.erb')
33
+ template = File.read(File.expand_path(template_file))
34
+
35
+ tree = root.as_json
36
+ result = ERB.new(template).result binding
37
+
38
+ if options.key?(:output)
39
+ File.write(options[:output], result)
40
+ else
41
+ puts result
42
+ end
@@ -0,0 +1,2 @@
1
+ require 'dirtree/version'
2
+ require 'dirtree/node'
@@ -0,0 +1,34 @@
1
+ module Dirtree
2
+ # tree node, it has a name and children of the same type
3
+ class Node
4
+ attr_reader :name, :children
5
+
6
+ def initialize(name)
7
+ @name = name
8
+ @children = []
9
+ end
10
+
11
+ # insert a node by its path, path is an array of names (Strings)
12
+ def insert(path)
13
+ return if path.empty?
14
+
15
+ child_name = path.first
16
+ grandchildren = path[1..-1]
17
+
18
+ child = @children.find { |current| current.name == child_name }
19
+ unless child
20
+ child = Node.new(child_name)
21
+ @children << child
22
+ end
23
+
24
+ child.insert(grandchildren)
25
+ end
26
+
27
+ def as_json
28
+ {
29
+ name: name,
30
+ children: children.map(&:as_json)
31
+ }
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ module Dirtree
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,366 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <meta charset="utf-8">
4
+ <style type="text/css">
5
+
6
+ html, body {
7
+ padding: 0px;
8
+ margin: 0px;
9
+ width: 100%;
10
+ height: 100%;
11
+ overflow: hidden;
12
+ }
13
+
14
+ .node {
15
+ cursor: pointer;
16
+ }
17
+
18
+ .overlay{
19
+ background-color:#EEE;
20
+ }
21
+
22
+ .node circle {
23
+ fill: #fff;
24
+ stroke: steelblue;
25
+ stroke-width: 1.5px;
26
+ }
27
+
28
+ .node text {
29
+ font-size:10px;
30
+ font-family:sans-serif;
31
+ }
32
+
33
+ .link {
34
+ fill: none;
35
+ stroke: #ccc;
36
+ stroke-width: 1.5px;
37
+ }
38
+ </style>
39
+ <body>
40
+ <div id="tree-container"></div>
41
+ <script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
42
+ <script src="http://d3js.org/d3.v3.min.js"></script>
43
+ <script>
44
+ /*Copyright (c) 2013-2016, Rob Schmuecker
45
+ All rights reserved.
46
+
47
+ Redistribution and use in source and binary forms, with or without
48
+ modification, are permitted provided that the following conditions are met:
49
+
50
+ * Redistributions of source code must retain the above copyright notice, this
51
+ list of conditions and the following disclaimer.
52
+
53
+ * Redistributions in binary form must reproduce the above copyright notice,
54
+ this list of conditions and the following disclaimer in the documentation
55
+ and/or other materials provided with the distribution.
56
+
57
+ * The name Rob Schmuecker may not be used to endorse or promote products
58
+ derived from this software without specific prior written permission.
59
+
60
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
61
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
62
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
63
+ DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
64
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
65
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
66
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
67
+ OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
68
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
69
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.*/
70
+
71
+ treeData = <%= tree.to_json %>;
72
+
73
+ // Calculate total nodes, max label length
74
+ var maxLabelLength = 0;
75
+ // panning variables
76
+ var panSpeed = 200;
77
+ // Misc. variables
78
+ var i = 0;
79
+ var duration = 750;
80
+ var root;
81
+
82
+ // size of the diagram
83
+ var viewerWidth = $(document).width();
84
+ var viewerHeight = $(document).height();
85
+
86
+ var tree = d3.layout.tree()
87
+ .size([viewerHeight, viewerWidth]);
88
+
89
+ // define a d3 diagonal projection for use by the node paths later on.
90
+ var diagonal = d3.svg.diagonal()
91
+ .projection(function(d) {
92
+ return [d.y, d.x];
93
+ });
94
+
95
+ // A recursive helper function for performing some setup by walking through all nodes
96
+ function visit(parent, visitFn, childrenFn) {
97
+ if (!parent) return;
98
+
99
+ visitFn(parent);
100
+
101
+ var children = childrenFn(parent);
102
+ if (children) {
103
+ var count = children.length;
104
+ for (var i = 0; i < count; i++) {
105
+ visit(children[i], visitFn, childrenFn);
106
+ }
107
+ }
108
+ }
109
+
110
+ // Call visit function to establish maxLabelLength
111
+ visit(treeData, function(d) {
112
+ maxLabelLength = Math.max(d.name.length, maxLabelLength);
113
+
114
+ }, function(d) {
115
+ return d.children && d.children.length > 0 ? d.children : null;
116
+ });
117
+
118
+ function pan(domNode, direction) {
119
+ var speed = panSpeed;
120
+ if (panTimer) {
121
+ clearTimeout(panTimer);
122
+ translateCoords = d3.transform(svgGroup.attr("transform"));
123
+ if (direction == 'left' || direction == 'right') {
124
+ translateX = direction == 'left' ? translateCoords.translate[0] + speed : translateCoords.translate[0] - speed;
125
+ translateY = translateCoords.translate[1];
126
+ } else if (direction == 'up' || direction == 'down') {
127
+ translateX = translateCoords.translate[0];
128
+ translateY = direction == 'up' ? translateCoords.translate[1] + speed : translateCoords.translate[1] - speed;
129
+ }
130
+ scaleX = translateCoords.scale[0];
131
+ scaleY = translateCoords.scale[1];
132
+ scale = zoomListener.scale();
133
+ svgGroup.transition().attr("transform", "translate(" + translateX + "," + translateY + ")scale(" + scale + ")");
134
+ d3.select(domNode).select('g.node').attr("transform", "translate(" + translateX + "," + translateY + ")");
135
+ zoomListener.scale(zoomListener.scale());
136
+ zoomListener.translate([translateX, translateY]);
137
+ panTimer = setTimeout(function() {
138
+ pan(domNode, speed, direction);
139
+ }, 50);
140
+ }
141
+ }
142
+
143
+ // Define the zoom function for the zoomable tree
144
+ function zoom() {
145
+ svgGroup.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
146
+ }
147
+
148
+
149
+ // define the zoomListener which calls the zoom function on the "zoom" event constrained within the scaleExtents
150
+ var zoomListener = d3.behavior.zoom().scaleExtent([0.1, 3]).on("zoom", zoom);
151
+
152
+ // define the baseSvg, attaching a class for styling and the zoomListener
153
+ var baseSvg = d3.select("#tree-container").append("svg")
154
+ .attr("width", viewerWidth)
155
+ .attr("height", viewerHeight)
156
+ .attr("class", "overlay")
157
+ .call(zoomListener);
158
+
159
+ // Function to center node when clicked/dropped so node doesn't get lost when collapsing/moving with large amount of children.
160
+
161
+ function centerNode(source) {
162
+ scale = zoomListener.scale();
163
+ x = -source.y0;
164
+ y = -source.x0;
165
+ x = x * scale + viewerWidth / 2;
166
+ y = y * scale + viewerHeight / 2;
167
+ d3.select('g').transition()
168
+ .duration(duration)
169
+ .attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
170
+ zoomListener.scale(scale);
171
+ zoomListener.translate([x, y]);
172
+ }
173
+
174
+ // Toggle children function
175
+
176
+ function toggleChildren(d) {
177
+ if (d.children) {
178
+ d._children = d.children;
179
+ d.children = null;
180
+ } else if (d._children) {
181
+ d.children = d._children;
182
+ d._children = null;
183
+ }
184
+ return d;
185
+ }
186
+
187
+ // Toggle children on click.
188
+ function click(d) {
189
+ if (d3.event.defaultPrevented) return; // click suppressed
190
+ d = toggleChildren(d);
191
+ update(d);
192
+ centerNode(d);
193
+ }
194
+
195
+ function update(source) {
196
+ // Compute the new height, function counts total children of root node and sets tree height accordingly.
197
+ // This prevents the layout looking squashed when new nodes are made visible or looking sparse when nodes are removed
198
+ // This makes the layout more consistent.
199
+ var levelWidth = [1];
200
+ var childCount = function(level, n) {
201
+
202
+ if (n.children && n.children.length > 0) {
203
+ if (levelWidth.length <= level + 1) levelWidth.push(0);
204
+
205
+ levelWidth[level + 1] += n.children.length;
206
+ n.children.forEach(function(d) {
207
+ childCount(level + 1, d);
208
+ });
209
+ }
210
+ };
211
+ childCount(0, root);
212
+ var newHeight = d3.max(levelWidth) * 25; // 25 pixels per line
213
+ tree = tree.size([newHeight, viewerWidth]);
214
+
215
+ // Compute the new tree layout.
216
+ var nodes = tree.nodes(root).reverse(),
217
+ links = tree.links(nodes);
218
+
219
+ // Set widths between levels based on maxLabelLength.
220
+ nodes.forEach(function(d) {
221
+ d.y = (d.depth * (maxLabelLength * 10)); //maxLabelLength * 10px
222
+ // alternatively to keep a fixed scale one can set a fixed depth per level
223
+ // Normalize for fixed-depth by commenting out below line
224
+ // d.y = (d.depth * 500); //500px per level.
225
+ });
226
+
227
+ // Update the nodes…
228
+ node = svgGroup.selectAll("g.node")
229
+ .data(nodes, function(d) {
230
+ return d.id || (d.id = ++i);
231
+ });
232
+
233
+ // Enter any new nodes at the parent's previous position.
234
+ var nodeEnter = node.enter().append("g")
235
+ .attr("class", "node")
236
+ .attr("transform", function(d) {
237
+ return "translate(" + source.y0 + "," + source.x0 + ")";
238
+ })
239
+ .on('click', click);
240
+
241
+ nodeEnter.append("circle")
242
+ .attr('class', 'nodeCircle')
243
+ .attr("r", 0)
244
+ .style("fill", function(d) {
245
+ return d._children ? "lightsteelblue" : "#fff";
246
+ });
247
+
248
+ nodeEnter.append("text")
249
+ .attr("x", function(d) {
250
+ return d.children || d._children ? -10 : 10;
251
+ })
252
+ .attr("dy", ".35em")
253
+ .attr('class', 'nodeText')
254
+ .attr("text-anchor", function(d) {
255
+ return d.children || d._children ? "end" : "start";
256
+ })
257
+ .text(function(d) {
258
+ return d.name;
259
+ })
260
+ .style("fill-opacity", 0);
261
+
262
+ // Update the text to reflect whether node has children or not.
263
+ node.select('text')
264
+ .attr("x", function(d) {
265
+ return d.children || d._children ? -10 : 10;
266
+ })
267
+ .attr("text-anchor", function(d) {
268
+ return d.children || d._children ? "end" : "start";
269
+ })
270
+ .text(function(d) {
271
+ return d.name;
272
+ });
273
+
274
+ // Change the circle fill depending on whether it has children and is collapsed
275
+ node.select("circle.nodeCircle")
276
+ .attr("r", 4.5)
277
+ .style("fill", function(d) {
278
+ return d._children ? "lightsteelblue" : "#fff";
279
+ });
280
+
281
+ // Transition nodes to their new position.
282
+ var nodeUpdate = node.transition()
283
+ .duration(duration)
284
+ .attr("transform", function(d) {
285
+ return "translate(" + d.y + "," + d.x + ")";
286
+ });
287
+
288
+ // Fade the text in
289
+ nodeUpdate.select("text")
290
+ .style("fill-opacity", 1);
291
+
292
+ // Transition exiting nodes to the parent's new position.
293
+ var nodeExit = node.exit().transition()
294
+ .duration(duration)
295
+ .attr("transform", function(d) {
296
+ return "translate(" + source.y + "," + source.x + ")";
297
+ })
298
+ .remove();
299
+
300
+ nodeExit.select("circle")
301
+ .attr("r", 0);
302
+
303
+ nodeExit.select("text")
304
+ .style("fill-opacity", 0);
305
+
306
+ // Update the links…
307
+ var link = svgGroup.selectAll("path.link")
308
+ .data(links, function(d) {
309
+ return d.target.id;
310
+ });
311
+
312
+ // Enter any new links at the parent's previous position.
313
+ link.enter().insert("path", "g")
314
+ .attr("class", "link")
315
+ .attr("d", function(d) {
316
+ var o = {
317
+ x: source.x0,
318
+ y: source.y0
319
+ };
320
+ return diagonal({
321
+ source: o,
322
+ target: o
323
+ });
324
+ });
325
+
326
+ // Transition links to their new position.
327
+ link.transition()
328
+ .duration(duration)
329
+ .attr("d", diagonal);
330
+
331
+ // Transition exiting nodes to the parent's new position.
332
+ link.exit().transition()
333
+ .duration(duration)
334
+ .attr("d", function(d) {
335
+ var o = {
336
+ x: source.x,
337
+ y: source.y
338
+ };
339
+ return diagonal({
340
+ source: o,
341
+ target: o
342
+ });
343
+ })
344
+ .remove();
345
+
346
+ // Stash the old positions for transition.
347
+ nodes.forEach(function(d) {
348
+ d.x0 = d.x;
349
+ d.y0 = d.y;
350
+ });
351
+ }
352
+
353
+ // Append a group which holds all nodes and which the zoom Listener can act upon.
354
+ var svgGroup = baseSvg.append("g");
355
+
356
+ // Define the root
357
+ root = treeData;
358
+ root.x0 = viewerHeight / 2;
359
+ root.y0 = 0;
360
+
361
+ // Layout the tree initially and center on the root node.
362
+ update(root);
363
+ centerNode(root);
364
+ </script>
365
+ </body>
366
+ </html>
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dirtree
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Emad Elsaid
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-06-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.15'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.15'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ description: display list of file paths as an interactive tree
42
+ email:
43
+ - blazeeboy@gmail.com
44
+ executables:
45
+ - dirtree
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".gitignore"
50
+ - Gemfile
51
+ - LICENSE.txt
52
+ - README.md
53
+ - Rakefile
54
+ - dirtree.gemspec
55
+ - exe/dirtree
56
+ - lib/dirtree.rb
57
+ - lib/dirtree/node.rb
58
+ - lib/dirtree/version.rb
59
+ - templates/tree.html.erb
60
+ homepage: https://www.github.com/blazeeboy/dirtree
61
+ licenses:
62
+ - MIT
63
+ metadata: {}
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubyforge_project:
80
+ rubygems_version: 2.6.12
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: display list of file paths as an interactive tree
84
+ test_files: []