revtree 0.1.3 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +24 -15
  3. data/lib/revtree.rb +121 -81
  4. metadata +62 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fdd5916237e12c049db40a28dfc46aae0d49a9da26d7f92dcce2f9c16ec1bfc5
4
- data.tar.gz: f8666a0a6d4684ea29b88dd603104e3affbff010e13991bc929e1f578b6ed6de
3
+ metadata.gz: 38695028f770cc56588f3101b95f6dfdde959306928750c41ee7b04694b25f93
4
+ data.tar.gz: 4b240bf3d9601e5cbb73592ccaa4b95de20a1164ce8a0506642c394a1d5959c0
5
5
  SHA512:
6
- metadata.gz: 8386219887c05bee8f4fe64c3b981a6772ccda56dcfe750115565aaf906d601019524ed5024a16ea97167e8630a4c88a6fc7d67b63c492f29b05d9832140cc7f
7
- data.tar.gz: f9a11c1f1e9fcc91564a6bb4d07f3ae373913c31c732c1edaef963c9b531754ae7ce592a2db6a3582e66fd2ca6767895b602a0840590bebcdbf6fb55f3564bbb
6
+ metadata.gz: 1b2db6f9de82d4b0b5adb8ea1fb0f562eec56e5242c97fddefde0240d6f0cbab0ade2f6baff811d579a139c85865d462ff27f94aed24de834d88975cb98277fd
7
+ data.tar.gz: 71c0386cd9c5d64e1881c47e6ab7ac83c5e5e03ceba87ff16c4fc63be1ef07795ac5cc8629ace349cc9315db8c3e955f21a21f488dfce61c0b0e6dbf607b8461
data/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # RevTree
2
2
 
3
+ [![GitHub Tag](https://img.shields.io/github/v/tag/juliankahlert/revtree)](https://github.com/juliankahlert/revtree)
4
+ [![Gem Version](https://img.shields.io/gem/v/revtree)](https://rubygems.org/gems/revtree)
5
+ [![Codacy Badge](https://app.codacy.com/project/badge/Grade/ac169e80b46b4d78a1a3e8e15be24c2f)](https://app.codacy.com/gh/juliankahlert/revtree/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
6
+ [![Codacy Badge](https://app.codacy.com/project/badge/Coverage/ac169e80b46b4d78a1a3e8e15be24c2f)](https://app.codacy.com/gh/juliankahlert/revtree/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_coverage)
7
+
3
8
  ## Introduction
4
9
 
5
10
  `RevTree` is a Ruby library for tracking and comparing directory structures.
@@ -20,16 +25,19 @@ It also provides methods to print and serialize the tree structure, as well as t
20
25
  ## Install
21
26
 
22
27
  The supported tools are:
28
+
23
29
  - gitpack
24
30
  - make
25
31
  - gem
26
32
 
27
33
  ### gitpack
34
+
28
35
  ```sh
29
36
  gitpack add juliankahlert/revtree
30
37
  ```
31
38
 
32
39
  ### make
40
+
33
41
  ```sh
34
42
  git clone https://github.com/juliankahlert/revtree.git
35
43
  cd revtree
@@ -37,11 +45,12 @@ sudo make install
37
45
  ```
38
46
 
39
47
  ### gem (local)
48
+
40
49
  ```sh
41
50
  git clone https://github.com/juliankahlert/revtree.git
42
51
  cd revtree
43
52
  gem build revtree.gemspec
44
- sudo gem install --local revtree-0.1.3.gem
53
+ sudo gem install --local revtree-0.1.5.gem
45
54
  ```
46
55
 
47
56
  ## API Documentation
@@ -49,52 +58,52 @@ sudo gem install --local revtree-0.1.3.gem
49
58
  ### `RevTree.new(path, whitelist = nil)`
50
59
 
51
60
  - **Parameters**:
52
- - `path` (String): The path to the file or directory.
53
- - `whitelist` (Array<String>): List of patterns to include in the tree.
61
+ - `path` (`String`): The path to the file or directory.
62
+ - `whitelist` (`Array<String>`): List of patterns to include in the tree.
54
63
 
55
64
  ### `#print_tree(indent = 0)`
56
65
 
57
66
  - **Parameters**:
58
- - `indent` (Integer): Number of spaces to indent each level of the tree.
67
+ - `indent` (`Integer`): Number of spaces to indent each level of the tree.
59
68
 
60
69
  ### `#to_h`
61
70
 
62
- - **Returns**: A Hash representation of the RevTree object.
71
+ - **Returns**: A `Hash` representation of the `RevTree` object.
63
72
 
64
73
  ### `#to_json`
65
74
 
66
- - **Returns**: A JSON representation of the RevTree object.
75
+ - **Returns**: A `JSON` representation of the `RevTree` object.
67
76
 
68
77
  ### `#for_each(status_whitelist, &block)`
69
78
 
70
79
  - **Parameters**:
71
- - `status_whitelist` (Array<Symbol>): List of statuses to include (e.g., `[:added, :removed]`).
72
- - `&block` (Proc): A block to execute for each file matching the given statuses.
80
+ - `status_whitelist` (`Array<Symbol>`): List of statuses to include (e.g., `[:added, :removed]`).
81
+ - `&block` (`Proc`): A block to execute for each file matching the given statuses.
73
82
  - **Behavior**: Iterates over files in the tree, executing the block for each file whose status matches one of the statuses in the whitelist.
74
83
 
75
84
  ### `#watch(status_whitelist = [:modified, :added, :removed], &block)`
76
85
 
77
86
  - **Parameters**:
78
- - `status_whitelist` (Array<Symbol>): List of statuses to watch (e.g., `[:added, :removed]`).
79
- - `&block` (Proc): A block to execute when a file matching the given statuses is changed.
87
+ - `status_whitelist` (`Array<Symbol>`): List of statuses to watch (e.g., `[:added, :removed]`).
88
+ - `&block` (`Proc`): A block to execute when a file matching the given statuses is changed.
80
89
 
81
90
  ### `#with_interval(interval)`
82
91
 
83
92
  - **Parameters**:
84
- - `interval` (Integer): Interval (in seconds) between checks for changes.
93
+ - `interval` (`Integer`): Interval (in seconds) between checks for changes.
85
94
  - **Returns**: The `RevTree` instance, enabling method chaining.
86
95
 
87
96
  ### `RevTree.from_h(h)`
88
97
 
89
98
  - **Parameters**:
90
- - `h` (Hash): A hash representation of a RevTree object.
91
- - **Returns**: A RevTree object.
99
+ - `h` (`Hash`): A `Hash` representation of a `RevTree` object.
100
+ - **Returns**: A `RevTree` object.
92
101
 
93
102
  ### `RevTree.from_json(json_str)`
94
103
 
95
104
  - **Parameters**:
96
- - `json_str` (String): A JSON string representing a RevTree object.
97
- - **Returns**: A RevTree object.
105
+ - `json_str` (`String`): A `JSON` string representing a `RevTree` object.
106
+ - **Returns**: A `RevTree` object.
98
107
 
99
108
  ## Example Usage
100
109
 
data/lib/revtree.rb CHANGED
@@ -51,11 +51,15 @@ class RevTree
51
51
  #
52
52
  # @param path [String, Pathname] the path to the file or directory
53
53
  # @param whitelist [Array<String>, nil] a list of file patterns to include (optional)
54
- def initialize(path, whitelist = nil)
54
+ def initialize(path, whitelist = ['*'])
55
55
  @path = Pathname.new(path)
56
56
  @name = @path.basename.to_s
57
- @whitelist = whitelist || []
57
+ @whitelist = whitelist
58
58
  @status = :unmodified
59
+ @type = :folder
60
+ @children = []
61
+ @interval = 5
62
+ @rev = ''
59
63
 
60
64
  if @path.directory?
61
65
  init_dir
@@ -69,8 +73,8 @@ class RevTree
69
73
  # @param indent [Integer] the indentation level (default: 0)
70
74
  # @return [void]
71
75
  def print_tree(indent = 0)
72
- status_str = @status ? " (status: #{@status})" : ''
73
- puts "#{' ' * indent}#{@type == :folder ? '[Folder]' : '[File]'} #{@name} (rev: #{@rev})#{status_str}"
76
+ indent_prefix = ' ' * indent
77
+ puts "#{indent_prefix}#{type_to_str()} #{@name} (rev: #{@rev}) #{status_to_str()}"
74
78
  @children.each { |child| child.print_tree(indent + 1) }
75
79
  end
76
80
 
@@ -96,10 +100,12 @@ class RevTree
96
100
 
97
101
  # Reconstructs a `RevTree` object from a hash.
98
102
  #
99
- # @param h [Hash] the hash to deserialize
103
+ # @param hash [Hash] the hash to deserialize
100
104
  # @return [RevTree] the reconstructed `RevTree` object
101
- def self.from_h(h)
102
- new_tree(h[:name], h[:type].to_sym, h[:rev], h[:children], h[:status])
105
+ def self.from_h(hash)
106
+ hash[:status] = hash[:status].to_sym
107
+ hash[:type] = hash[:type].to_sym
108
+ new_tree(hash)
103
109
  end
104
110
 
105
111
  # Reconstructs a `RevTree` object from a JSON string.
@@ -123,7 +129,7 @@ class RevTree
123
129
  def for_each(status_whitelist = [:unmodified, :modified, :added, :removed], &block)
124
130
  return unless block_given?
125
131
 
126
- RevTree.traverse_tree(self, status_whitelist, @path, &block)
132
+ RevTree.traverse_tree(self, status_whitelist, @path, nil, &block)
127
133
  end
128
134
 
129
135
  # Watches the tree for changes
@@ -138,23 +144,12 @@ class RevTree
138
144
  # @return [void]
139
145
  def watch(status_whitelist = [:modified, :added, :removed], &block)
140
146
  current_tree = self
141
- @interval ||= 5
147
+ setup_traps
142
148
 
143
- Signal.trap('INT') { exit }
144
- Signal.trap('TERM') { exit }
145
149
  loop do
146
150
  sleep @interval
147
151
 
148
- new_tree = RevTree.new(@path, @whitelist)
149
- diff_tree = RevTree.compare(current_tree, new_tree)
150
-
151
- next if diff_tree.nil?
152
-
153
- diff_tree.for_each(status_whitelist) do |node, full_path|
154
- block.call(node, full_path)
155
- end
156
-
157
- current_tree = new_tree
152
+ current_tree = refresh_tree(current_tree, status_whitelist, block)
158
153
  end
159
154
  end
160
155
 
@@ -169,6 +164,45 @@ class RevTree
169
164
 
170
165
  private
171
166
 
167
+ # Refreshes the tree by comparing the current tree with a new version and executing a block
168
+ # for each file that matches the given statuses.
169
+ #
170
+ # @param current_tree [RevTree] the current RevTree instance
171
+ # @param status_whitelist [Array<Symbol>] the list of statuses to match (e.g., :added, :modified, :removed)
172
+ # @param block [Proc] the block to execute for each file matching the statuses
173
+ # @return [RevTree] the updated RevTree with changes
174
+ def refresh_tree(current_tree, status_whitelist, block)
175
+ new_tree = RevTree.new(@path, @whitelist)
176
+ diff_tree = RevTree.compare(current_tree, new_tree)
177
+ return current_tree unless diff_tree
178
+
179
+ diff_tree.for_each(status_whitelist, &block)
180
+
181
+ new_tree
182
+ end
183
+
184
+ # Setup traps.
185
+ #
186
+ # @return [void]
187
+ def setup_traps
188
+ Signal.trap('INT') { exit }
189
+ Signal.trap('TERM') { exit }
190
+ end
191
+
192
+ # Stringify the type for pretty-printing.
193
+ #
194
+ # @return [String] the type string
195
+ def type_to_str
196
+ @type == :folder ? '[Folder]' : '[File]'
197
+ end
198
+
199
+ # Stringify the status for pretty-printing.
200
+ #
201
+ # @return [String] the status string
202
+ def status_to_str
203
+ @status ? "(status: #{@status})" : ''
204
+ end
205
+
172
206
  # Calculates the MD5 hash for the file.
173
207
  #
174
208
  # @return [String] the MD5 hash of the file
@@ -187,10 +221,9 @@ class RevTree
187
221
  #
188
222
  # @return [void]
189
223
  def init_dir
190
- @type = :folder
191
224
  @children = @path.children
192
- .select { |c| include_in_tree?(c) }
193
- .map { |c| RevTree.new(c, @whitelist) }
225
+ .select { |child| include_in_tree?(child) }
226
+ .map { |child| RevTree.new(child, @whitelist) }
194
227
  @rev = calculate_directory_rev
195
228
  end
196
229
 
@@ -199,43 +232,61 @@ class RevTree
199
232
  # @return [void]
200
233
  def init_file
201
234
  @type = :file
202
- @children = []
203
235
  @rev = calculate_file_rev
204
236
  end
205
237
 
238
+ # Apply attributes from a `Hash` to a `RevTree`.
239
+ #
240
+ # @param revtree [RevTree] the tree node to be modified
241
+ # @param attr_hash [Hash] the hash containing the attributes
242
+ # @return [void]
243
+ def self.apply_attributes(revtree, attr_hash)
244
+ revtree.instance_variable_set(:@name, attr_hash[:name])
245
+ revtree.instance_variable_set(:@type, attr_hash[:type])
246
+ revtree.instance_variable_set(:@rev, attr_hash[:rev])
247
+ revtree.instance_variable_set(:@status, attr_hash[:status])
248
+ revtree.instance_variable_set(:@children, attr_hash[:children])
249
+ end
250
+
251
+ # Recurse into child nodes to deserialize `RevTree`.
252
+ #
253
+ # @param hash [Hash] the hash to deserialize
254
+ # @return [void]
255
+ def self.new_tree_recurse_children(hash)
256
+ children = hash[:children] || []
257
+ hash[:children] = children.map { |child| from_h(child) }
258
+ end
259
+
206
260
  # Rebuilds a `RevTree` from its serialized components.
207
261
  #
208
- # @param name [String] the name of the node
209
- # @param type [Symbol] the type of the node (:folder or :file)
210
- # @param rev [String] the revision hash of the node
211
- # @param children [Array<Hash>] the child nodes (if any)
212
- # @param status [Symbol] the status of the node (:unmodified, :modified, etc.)
262
+ # @param hash [Hash] the hash to deserialize
213
263
  # @return [RevTree] the reconstructed tree
214
- def self.new_tree(name, type, rev, children, status = :unmodified)
264
+ def self.new_tree(hash)
215
265
  tree = allocate
216
- tree.instance_variable_set(:@name, name)
217
- tree.instance_variable_set(:@type, type.to_sym)
218
- tree.instance_variable_set(:@rev, rev)
219
- tree.instance_variable_set(:@status, status.to_sym)
220
- if type == :folder && children
221
- tree.instance_variable_set(:@children, children.map { |c| from_h(c) })
222
- else
223
- tree.instance_variable_set(:@children, [])
224
- end
266
+ new_tree_recurse_children(hash)
267
+ apply_attributes(tree, hash)
225
268
  tree
226
269
  end
227
270
 
271
+ # Determines whether a path is a dot directory.
272
+ #
273
+ # @param path [Pathname] the path to the file or directory
274
+ # @return [Boolean] `true` if path is a dot directory, `false` otherwise
275
+ def self.path_is_dot_dir?(path)
276
+ path.directory? && path.basename.to_s.start_with?('.')
277
+ end
278
+
228
279
  # Determines whether a file or directory should be included in the tree.
229
280
  #
230
281
  # @param path [Pathname] the path to the file or directory
231
282
  # @return [Boolean] `true` if the path should be included, `false` otherwise
232
283
  def include_in_tree?(path)
233
- return false if path.directory? && path.basename.to_s.start_with?('.')
284
+ return false if RevTree.path_is_dot_dir?(path)
234
285
 
235
286
  return true if path.directory?
236
287
  return true if @whitelist.empty?
237
288
 
238
- @whitelist.any? { |p| File.fnmatch?(p, path.basename.to_s) }
289
+ @whitelist.any? { |pattern| File.fnmatch?(pattern, path.basename.to_s) }
239
290
  end
240
291
 
241
292
  # Compares two `RevTree` nodes (old and new) and returns a tree with appropriate status.
@@ -244,16 +295,13 @@ class RevTree
244
295
  # @param new [RevTree, nil] the new version of the tree
245
296
  # @return [RevTree, nil] the resulting tree with status updates or `nil`
246
297
  def self.compare(old, new)
247
- return nil if old.nil? && new.nil?
298
+ return nil unless old || new
248
299
 
249
- return handle_addition(new) if old.nil?
250
- return handle_removal(old) if new.nil?
300
+ return handle_addition(new) unless old
301
+ return handle_removal(old) unless new
251
302
 
252
- if old.rev != new.rev
253
- return handle_modification(old, new)
254
- else
255
- return handle_unmodified(old, new)
256
- end
303
+ status = old.rev != new.rev ? :modified : :unmodified
304
+ handle_modification(old, new, status)
257
305
  end
258
306
 
259
307
  # Handles the addition of a new node.
@@ -280,13 +328,14 @@ class RevTree
280
328
  #
281
329
  # @param old [RevTree] the old node
282
330
  # @param new [RevTree] the new node
331
+ # @param status [Symbol] the status to apply (:modified or :unmodified)
283
332
  # @return [RevTree] the node with the status set to `:modified`
284
- def self.handle_modification(old, new)
333
+ def self.handle_modification(old, new, status)
285
334
  if old.type == :folder && new.type == :folder
286
- compare_folders(old, new, :modified)
335
+ compare_folders(old, new, status)
287
336
  else
288
337
  with_status = new.dup
289
- with_status.instance_variable_set(:@status, :modified)
338
+ with_status.instance_variable_set(:@status, status)
290
339
  with_status
291
340
  end
292
341
  end
@@ -300,7 +349,9 @@ class RevTree
300
349
  def self.compare_folders(old, new, status)
301
350
  combined_children = merge_children(old.children, new.children)
302
351
  with_status = new.dup
303
- merged = combined_children.map { |o, n| compare(o, n) }
352
+ merged = combined_children.map do |old_child, new_child|
353
+ compare(old_child, new_child)
354
+ end
304
355
 
305
356
  folder_status = merged.any? { |child| child.status == :modified } ? :modified : status
306
357
 
@@ -309,53 +360,42 @@ class RevTree
309
360
  with_status
310
361
  end
311
362
 
312
- # Handles the unmodified status of a node.
313
- #
314
- # @param old [RevTree] the old node
315
- # @param new [RevTree] the new node
316
- # @return [RevTree] the node with the status set to `:unmodified`
317
- def self.handle_unmodified(old, new)
318
- if old.type == :folder && new.type == :folder
319
- compare_folders(old, new, :unmodified)
320
- else
321
- with_status = new.dup
322
- with_status.instance_variable_set(:@status, :unmodified)
323
- with_status
324
- end
325
- end
326
-
327
363
  # Merges the children of two nodes.
328
364
  #
329
- # @param old_children [Array<RevTree>] the children of the old node
330
- # @param new_children [Array<RevTree>] the children of the new node
331
365
  # @return [Array<Array<RevTree, RevTree>>] an array of paired old and new children
332
366
  def self.merge_children(old_children, new_children)
333
367
  all_names = (old_children.map(&:name) + new_children.map(&:name)).uniq
334
- all_names.map do |name|
335
- old_child = old_children.find { |child| child.name == name }
336
- new_child = new_children.find { |child| child.name == name }
337
- [old_child, new_child]
338
- end
368
+ all_names.map { |name| find_child_pair(name, old_children, new_children) }
369
+ end
370
+
371
+ # @param old_children [Array<RevTree>] the children of the old node
372
+ # @param new_children [Array<RevTree>] the children of the new node
373
+ def self.find_child_pair(name, old_children, new_children)
374
+ old_child = old_children.find { |old| old.name == name }
375
+ new_child = new_children.find { |new| new.name == name }
376
+ [old_child, new_child]
339
377
  end
340
378
 
341
379
  # Traverses the tree and executes a block for each file matching the provided status whitelist.
342
380
  #
343
381
  # @param node [RevTree] the current node being traversed
344
382
  # @param status_whitelist [Array<Symbol>] the list of statuses to match
383
+ # @param root [Pathname] the root path
345
384
  # @param current_path [Pathname] the current path
346
385
  # @yield [node, full_path] the block to be executed for each matching file
347
386
  # @yieldparam node [RevTree] the current node being traversed
348
387
  # @yieldparam full_path [String] the full path of the current node
349
388
  # @return [void]
350
- def self.traverse_tree(node, status_whitelist, current_path, &block)
389
+ def self.traverse_tree(node, status_whitelist, root, current_path, &block)
390
+ current_path = current_path.to_s
391
+ full_path = current_path == '' ? root : File.join(current_path, node.name.to_s)
392
+
351
393
  if node.type == :file && status_whitelist.include?(node.status)
352
- block.call(node, File.expand_path(current_path.to_s))
394
+ block.call(node, File.expand_path(current_path))
353
395
  end
354
396
 
355
- full_path = File.join(current_path.to_s, node.name.to_s)
356
-
357
397
  node.children.each do |child|
358
- traverse_tree(child, status_whitelist, full_path, &block)
398
+ traverse_tree(child, status_whitelist, root, full_path, &block)
359
399
  end
360
400
  end
361
401
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: revtree
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julian Kahlert
@@ -10,6 +10,66 @@ bindir: bin
10
10
  cert_chain: []
11
11
  date: 2024-09-07 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: simplecov-simplecov-cobertura
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: '2.1'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '2'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '2.1'
33
+ - !ruby/object:Gem::Dependency
34
+ name: simplecov-console
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.9'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 0.9.1
43
+ type: :development
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '0.9'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 0.9.1
53
+ - !ruby/object:Gem::Dependency
54
+ name: simplecov
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '0.22'
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 0.22.0
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '0.22'
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 0.22.0
13
73
  - !ruby/object:Gem::Dependency
14
74
  name: yard
15
75
  requirement: !ruby/object:Gem::Requirement
@@ -66,7 +126,7 @@ licenses:
66
126
  - MIT
67
127
  metadata:
68
128
  homepage_uri: https://juliankahlert.github.io/revtree/
69
- documentation_uri: https://www.rubydoc.info/gems/revtree/0.1.3
129
+ documentation_uri: https://www.rubydoc.info/gems/revtree/0.1.5
70
130
  source_code_uri: https://github.com/juliankahlert/revtree
71
131
  post_install_message:
72
132
  rdoc_options: []