revtree 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +36 -2
- data/lib/revtree.rb +166 -12
- metadata +27 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e1e3a7315f79eab2d444a7b708fa4412dc14fa0d17aff060848a74c4275eefcc
|
4
|
+
data.tar.gz: 712bbd90a68a4cd071c1bd647cb9c36752aa01c0c0122cff32d585895695bd1f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: abeccda61e8bd8afbdc553550d43a22e786e20e57e0563f2d3e64e8accf2b67152cfc449b9417a8f10ecc3acf9bd58be2a6cde8402d1d0709586368b3f34054b
|
7
|
+
data.tar.gz: a90318c8711df1e9ce50d0fde521c7713e4923316be3e5f65e18a73489fbb76a3b9563651a2031f5ba69e19c11f511eeb713f2700e85c64a13cb22817ed5f2f1
|
data/README.md
CHANGED
@@ -14,6 +14,8 @@ It also provides methods to print and serialize the tree structure, as well as t
|
|
14
14
|
- **Serialization**: Supports serialization to and from JSON format for easy storage and transfer.
|
15
15
|
- **Pretty Printing**: Provides a method to print the directory tree with statuses.
|
16
16
|
- **Iterate with `for_each`**: Traverse the tree and apply a block to files matching specific statuses.
|
17
|
+
- **Watch for Changes**: Automatically monitors a directory for changes with `watch`, and triggers actions based on file changes.
|
18
|
+
- **Customizable Watch Interval**: Set a custom interval between checks with the `with_interval` method.
|
17
19
|
|
18
20
|
## Install
|
19
21
|
|
@@ -39,7 +41,7 @@ sudo make install
|
|
39
41
|
git clone https://github.com/juliankahlert/revtree.git
|
40
42
|
cd revtree
|
41
43
|
gem build revtree.gemspec
|
42
|
-
sudo gem install --local revtree-0.1.
|
44
|
+
sudo gem install --local revtree-0.1.2.gem
|
43
45
|
```
|
44
46
|
|
45
47
|
## API Documentation
|
@@ -70,6 +72,18 @@ sudo gem install --local revtree-0.1.0.gem
|
|
70
72
|
- `&block` (Proc): A block to execute for each file matching the given statuses.
|
71
73
|
- **Behavior**: Iterates over files in the tree, executing the block for each file whose status matches one of the statuses in the whitelist.
|
72
74
|
|
75
|
+
### `#watch(status_whitelist = [:modified, :added, :removed], &block)`
|
76
|
+
|
77
|
+
- **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.
|
80
|
+
|
81
|
+
### `#with_interval(interval)`
|
82
|
+
|
83
|
+
- **Parameters**:
|
84
|
+
- `interval` (Integer): Interval (in seconds) between checks for changes.
|
85
|
+
- **Returns**: The `RevTree` instance, enabling method chaining.
|
86
|
+
|
73
87
|
### `RevTree.from_h(h)`
|
74
88
|
|
75
89
|
- **Parameters**:
|
@@ -84,6 +98,8 @@ sudo gem install --local revtree-0.1.0.gem
|
|
84
98
|
|
85
99
|
## Example Usage
|
86
100
|
|
101
|
+
### Comparing two directory structures
|
102
|
+
|
87
103
|
```ruby
|
88
104
|
#!/bin/env ruby
|
89
105
|
|
@@ -105,9 +121,27 @@ result_tree.for_each([:added, :removed]) do |file, full_path|
|
|
105
121
|
end
|
106
122
|
```
|
107
123
|
|
124
|
+
### Watching a directory for changes
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
#!/bin/env ruby
|
128
|
+
|
129
|
+
require 'revtree'
|
130
|
+
|
131
|
+
# Create a RevTree for the current directory, including only .rb and .md files
|
132
|
+
file_tree = RevTree.new('./', ['*.rb', '*.md'])
|
133
|
+
|
134
|
+
# Watch for changes in the directory
|
135
|
+
file_tree.with_interval(10).watch([:modified, :added, :removed]) do |file, full_path|
|
136
|
+
puts "File #{file.name} #{file.status} in #{full_path}"
|
137
|
+
end
|
138
|
+
```
|
139
|
+
|
140
|
+
This second example demonstrates the `watch` method, which continuously monitors the directory tree for changes and triggers a block of code when changes (additions, modifications, or deletions) are detected. The `with_interval` method customizes the time between checks.
|
141
|
+
|
108
142
|
## Encouragement for Contribution
|
109
143
|
|
110
|
-
|
144
|
+
Contributions from the community are welcome!
|
111
145
|
If you find any issues or have ideas for new features, please feel free to submit a pull request or open an issue.
|
112
146
|
Your input helps make RevTree better for everyone.
|
113
147
|
|
data/lib/revtree.rb
CHANGED
@@ -26,9 +26,31 @@ require 'digest'
|
|
26
26
|
require 'pathname'
|
27
27
|
require 'json'
|
28
28
|
|
29
|
+
# The `RevTree` class provides a tree structure representing file directories and
|
30
|
+
# files, allowing for version tracking based on MD5 hashes.
|
31
|
+
#
|
32
|
+
# This class can traverse directories, compare versions of trees, and serialize/deserialize
|
33
|
+
# itself to/from JSON.
|
29
34
|
class RevTree
|
30
|
-
|
35
|
+
# @return [Array<RevTree>] the list of children in the tree (empty for files)
|
36
|
+
attr_reader :children
|
37
|
+
|
38
|
+
# @return [Symbol] the type of the node (:folder or :file)
|
39
|
+
attr_reader :type
|
40
|
+
|
41
|
+
# @return [String] the name of the file or directory
|
42
|
+
attr_reader :name
|
43
|
+
|
44
|
+
# @return [String] the revision (MD5 hash) of the file or directory
|
45
|
+
attr_reader :rev
|
31
46
|
|
47
|
+
# @return [Symbol] the status of the file or directory (:unmodified, :modified, :added, :removed)
|
48
|
+
attr_reader :status
|
49
|
+
|
50
|
+
# Initializes a new `RevTree` object representing a directory or file.
|
51
|
+
#
|
52
|
+
# @param path [String, Pathname] the path to the file or directory
|
53
|
+
# @param whitelist [Array<String>, nil] a list of file patterns to include (optional)
|
32
54
|
def initialize(path, whitelist = nil)
|
33
55
|
@path = Pathname.new(path)
|
34
56
|
@name = @path.basename.to_s
|
@@ -42,21 +64,19 @@ class RevTree
|
|
42
64
|
end
|
43
65
|
end
|
44
66
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
def calculate_directory_rev
|
50
|
-
Digest::MD5.hexdigest(@children.map(&:rev).join)
|
51
|
-
end
|
52
|
-
|
67
|
+
# Prints the tree structure, including file names and statuses, to the console.
|
68
|
+
#
|
69
|
+
# @param indent [Integer] the indentation level (default: 0)
|
70
|
+
# @return [void]
|
53
71
|
def print_tree(indent = 0)
|
54
72
|
status_str = @status ? " (status: #{@status})" : ''
|
55
73
|
puts "#{' ' * indent}#{@type == :folder ? '[Folder]' : '[File]'} #{@name} (rev: #{@rev})#{status_str}"
|
56
74
|
@children.each { |child| child.print_tree(indent + 1) }
|
57
75
|
end
|
58
76
|
|
59
|
-
#
|
77
|
+
# Serializes the `RevTree` object to a hash.
|
78
|
+
#
|
79
|
+
# @return [Hash] a hash representing the object
|
60
80
|
def to_h
|
61
81
|
{
|
62
82
|
type: @type,
|
@@ -67,14 +87,25 @@ class RevTree
|
|
67
87
|
}
|
68
88
|
end
|
69
89
|
|
70
|
-
|
90
|
+
# Converts the `RevTree` object to JSON format.
|
91
|
+
#
|
92
|
+
# @return [String] a JSON string representing the object
|
93
|
+
def to_json
|
71
94
|
JSON.pretty_generate(self.to_h)
|
72
95
|
end
|
73
96
|
|
97
|
+
# Reconstructs a `RevTree` object from a hash.
|
98
|
+
#
|
99
|
+
# @param h [Hash] the hash to deserialize
|
100
|
+
# @return [RevTree] the reconstructed `RevTree` object
|
74
101
|
def self.from_h(h)
|
75
102
|
new_tree(h[:name], h[:type].to_sym, h[:rev], h[:children], h[:status])
|
76
103
|
end
|
77
104
|
|
105
|
+
# Reconstructs a `RevTree` object from a JSON string.
|
106
|
+
#
|
107
|
+
# @param json_str [String] the JSON string to deserialize
|
108
|
+
# @return [RevTree] the reconstructed `RevTree` object
|
78
109
|
def self.from_json(json_str)
|
79
110
|
data = JSON.parse(json_str, symbolize_names: true)
|
80
111
|
file_tree = from_h(data)
|
@@ -82,14 +113,79 @@ class RevTree
|
|
82
113
|
file_tree
|
83
114
|
end
|
84
115
|
|
85
|
-
|
116
|
+
# Executes a block of code for each file matching the provided status whitelist.
|
117
|
+
#
|
118
|
+
# @param status_whitelist [Array<Symbol>] the list of statuses to match (:added, :modified, etc.)
|
119
|
+
# @yield [node, full_path] the block to be executed for each matching file
|
120
|
+
# @yieldparam node [RevTree] the current node being traversed
|
121
|
+
# @yieldparam full_path [String] the full path of the current node
|
122
|
+
# @return [void]
|
123
|
+
def for_each(status_whitelist = [:unmodified, :modified, :added, :removed], &block)
|
86
124
|
return unless block_given?
|
87
125
|
|
88
126
|
RevTree.traverse_tree(self, status_whitelist, @path, &block)
|
89
127
|
end
|
90
128
|
|
129
|
+
# Watches the tree for changes
|
130
|
+
#
|
131
|
+
# Compares the refreshed tree to its last version and calls the provided block
|
132
|
+
# for each node that matches the statuses in the `status_whitelist`.
|
133
|
+
#
|
134
|
+
# @param status_whitelist [Array<Symbol>] the list of statuses to match (:added, :modified, etc.)
|
135
|
+
# @yield [node, full_path] the block to be executed for each matching file
|
136
|
+
# @yieldparam node [RevTree] the node that was changed
|
137
|
+
# @yieldparam full_path [String] the full path of the node
|
138
|
+
# @return [void]
|
139
|
+
def watch(status_whitelist = [:modified, :added, :removed], &block)
|
140
|
+
current_tree = self
|
141
|
+
@interval ||= 5
|
142
|
+
|
143
|
+
Signal.trap('INT') { exit }
|
144
|
+
Signal.trap('TERM') { exit }
|
145
|
+
loop do
|
146
|
+
sleep @interval
|
147
|
+
|
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
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Sets the interval for the watch method's sleep duration.
|
162
|
+
#
|
163
|
+
# @param interval [Integer] the number of seconds to sleep between checks
|
164
|
+
# @return [RevTree] the current instance for chaining
|
165
|
+
def with_interval(interval)
|
166
|
+
@interval = interval
|
167
|
+
self
|
168
|
+
end
|
169
|
+
|
91
170
|
private
|
92
171
|
|
172
|
+
# Calculates the MD5 hash for the file.
|
173
|
+
#
|
174
|
+
# @return [String] the MD5 hash of the file
|
175
|
+
def calculate_file_rev
|
176
|
+
Digest::MD5.file(@path).hexdigest
|
177
|
+
end
|
178
|
+
|
179
|
+
# Calculates the MD5 hash for the directory based on its children.
|
180
|
+
#
|
181
|
+
# @return [String] the MD5 hash of the directory
|
182
|
+
def calculate_directory_rev
|
183
|
+
Digest::MD5.hexdigest(@children.map(&:rev).join)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Initializes the directory node by traversing its children.
|
187
|
+
#
|
188
|
+
# @return [void]
|
93
189
|
def init_dir
|
94
190
|
@type = :folder
|
95
191
|
@children = @path.children
|
@@ -98,12 +194,23 @@ class RevTree
|
|
98
194
|
@rev = calculate_directory_rev
|
99
195
|
end
|
100
196
|
|
197
|
+
# Initializes the file node by calculating its revision.
|
198
|
+
#
|
199
|
+
# @return [void]
|
101
200
|
def init_file
|
102
201
|
@type = :file
|
103
202
|
@children = []
|
104
203
|
@rev = calculate_file_rev
|
105
204
|
end
|
106
205
|
|
206
|
+
# Rebuilds a `RevTree` from its serialized components.
|
207
|
+
#
|
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.)
|
213
|
+
# @return [RevTree] the reconstructed tree
|
107
214
|
def self.new_tree(name, type, rev, children, status = :unmodified)
|
108
215
|
tree = allocate
|
109
216
|
tree.instance_variable_set(:@name, name)
|
@@ -118,6 +225,10 @@ class RevTree
|
|
118
225
|
tree
|
119
226
|
end
|
120
227
|
|
228
|
+
# Determines whether a file or directory should be included in the tree.
|
229
|
+
#
|
230
|
+
# @param path [Pathname] the path to the file or directory
|
231
|
+
# @return [Boolean] `true` if the path should be included, `false` otherwise
|
121
232
|
def include_in_tree?(path)
|
122
233
|
return false if path.directory? && path.basename.to_s.start_with?('.')
|
123
234
|
|
@@ -127,6 +238,11 @@ class RevTree
|
|
127
238
|
@whitelist.any? { |p| File.fnmatch?(p, path.basename.to_s) }
|
128
239
|
end
|
129
240
|
|
241
|
+
# Compares two `RevTree` nodes (old and new) and returns a tree with appropriate status.
|
242
|
+
#
|
243
|
+
# @param old [RevTree, nil] the old version of the tree
|
244
|
+
# @param new [RevTree, nil] the new version of the tree
|
245
|
+
# @return [RevTree, nil] the resulting tree with status updates or `nil`
|
130
246
|
def self.compare(old, new)
|
131
247
|
return nil if old.nil? && new.nil?
|
132
248
|
|
@@ -140,18 +256,31 @@ class RevTree
|
|
140
256
|
end
|
141
257
|
end
|
142
258
|
|
259
|
+
# Handles the addition of a new node.
|
260
|
+
#
|
261
|
+
# @param new [RevTree] the new node
|
262
|
+
# @return [RevTree] the node with the status set to `:added`
|
143
263
|
def self.handle_addition(new)
|
144
264
|
with_status = new.dup
|
145
265
|
with_status.instance_variable_set(:@status, :added)
|
146
266
|
with_status
|
147
267
|
end
|
148
268
|
|
269
|
+
# Handles the removal of an old node.
|
270
|
+
#
|
271
|
+
# @param old [RevTree] the old node
|
272
|
+
# @return [RevTree] the node with the status set to `:removed`
|
149
273
|
def self.handle_removal(old)
|
150
274
|
with_status = old.dup
|
151
275
|
with_status.instance_variable_set(:@status, :removed)
|
152
276
|
with_status
|
153
277
|
end
|
154
278
|
|
279
|
+
# Handles the modification of a node.
|
280
|
+
#
|
281
|
+
# @param old [RevTree] the old node
|
282
|
+
# @param new [RevTree] the new node
|
283
|
+
# @return [RevTree] the node with the status set to `:modified`
|
155
284
|
def self.handle_modification(old, new)
|
156
285
|
if old.type == :folder && new.type == :folder
|
157
286
|
compare_folders(old, new, :modified)
|
@@ -162,6 +291,12 @@ class RevTree
|
|
162
291
|
end
|
163
292
|
end
|
164
293
|
|
294
|
+
# Compares two folder nodes and returns a merged node with status updates.
|
295
|
+
#
|
296
|
+
# @param old [RevTree] the old folder node
|
297
|
+
# @param new [RevTree] the new folder node
|
298
|
+
# @param status [Symbol] the status to apply (:modified or :unmodified)
|
299
|
+
# @return [RevTree] the resulting folder node with status updates
|
165
300
|
def self.compare_folders(old, new, status)
|
166
301
|
combined_children = merge_children(old.children, new.children)
|
167
302
|
with_status = new.dup
|
@@ -174,6 +309,11 @@ class RevTree
|
|
174
309
|
with_status
|
175
310
|
end
|
176
311
|
|
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`
|
177
317
|
def self.handle_unmodified(old, new)
|
178
318
|
if old.type == :folder && new.type == :folder
|
179
319
|
compare_folders(old, new, :unmodified)
|
@@ -184,6 +324,11 @@ class RevTree
|
|
184
324
|
end
|
185
325
|
end
|
186
326
|
|
327
|
+
# Merges the children of two nodes.
|
328
|
+
#
|
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
|
+
# @return [Array<Array<RevTree, RevTree>>] an array of paired old and new children
|
187
332
|
def self.merge_children(old_children, new_children)
|
188
333
|
all_names = (old_children.map(&:name) + new_children.map(&:name)).uniq
|
189
334
|
all_names.map do |name|
|
@@ -193,6 +338,15 @@ class RevTree
|
|
193
338
|
end
|
194
339
|
end
|
195
340
|
|
341
|
+
# Traverses the tree and executes a block for each file matching the provided status whitelist.
|
342
|
+
#
|
343
|
+
# @param node [RevTree] the current node being traversed
|
344
|
+
# @param status_whitelist [Array<Symbol>] the list of statuses to match
|
345
|
+
# @param current_path [Pathname] the current path
|
346
|
+
# @yield [node, full_path] the block to be executed for each matching file
|
347
|
+
# @yieldparam node [RevTree] the current node being traversed
|
348
|
+
# @yieldparam full_path [String] the full path of the current node
|
349
|
+
# @return [void]
|
196
350
|
def self.traverse_tree(node, status_whitelist, current_path, &block)
|
197
351
|
if node.type == :file && status_whitelist.include?(node.status)
|
198
352
|
block.call(node, File.expand_path(current_path.to_s))
|
metadata
CHANGED
@@ -1,15 +1,35 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: revtree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Julian Kahlert
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-09-
|
11
|
+
date: 2024-09-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: yard
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.9'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.9.37
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.9'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.9.37
|
13
33
|
- !ruby/object:Gem::Dependency
|
14
34
|
name: rspec
|
15
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -31,7 +51,8 @@ dependencies:
|
|
31
51
|
- !ruby/object:Gem::Version
|
32
52
|
version: '3.4'
|
33
53
|
description: RevTree builds a recursive directory tree and compares file revisions.
|
34
|
-
It can mark files and folders as added, removed, modified, or unmodified.
|
54
|
+
It can mark files and folders as added, removed, modified, or unmodified. For convenience,
|
55
|
+
RevTree also allows you to watch for changes in a directory tree.
|
35
56
|
email:
|
36
57
|
- 90937526+juliankahlert@users.noreply.github.com
|
37
58
|
executables: []
|
@@ -43,7 +64,9 @@ files:
|
|
43
64
|
homepage: https://github.com/juliankahlert/revtree
|
44
65
|
licenses:
|
45
66
|
- MIT
|
46
|
-
metadata:
|
67
|
+
metadata:
|
68
|
+
homepage_uri: https://github.com/juliankahlert/revtree
|
69
|
+
source_code_uri: https://github.com/juliankahlert/revtree
|
47
70
|
post_install_message:
|
48
71
|
rdoc_options: []
|
49
72
|
require_paths:
|