revtree 0.1.0
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 +7 -0
- data/README.md +116 -0
- data/lib/revtree.rb +207 -0
- metadata +66 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a7c82aecb58c070a313c7d7e7897c7c038d66dfa35d717fae991eeeb19245f6f
|
4
|
+
data.tar.gz: 6ffc2b90332f026b0cd32c7f6daf7af377e28ced7c679633d6807416d1e9e1ff
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b21ee3950eae8d1c09f2d42124d68653c0c21f1f4c0efe1e582d9444cb4bcc90620cb8778a7ed900aeba9f3beab85ca6cca45dfa596ec3f49f29240b7a33cb13
|
7
|
+
data.tar.gz: 2c0762cb314b65c1c14b8904cef4c18d0d434001193fe84d7f136fe9923de4cfa3bf73bcc253ce295dacb6ecb941479f8f84547fa3760c49006b305c31e720e0
|
data/README.md
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
# RevTree
|
2
|
+
|
3
|
+
## Introduction
|
4
|
+
|
5
|
+
`RevTree` is a Ruby library for tracking and comparing directory structures.
|
6
|
+
It calculates the MD5 hash for files and directories to determine their revisions and supports detecting changes like additions, removals, and modifications.
|
7
|
+
It also provides methods to print and serialize the tree structure, as well as traverse it for specific statuses.
|
8
|
+
|
9
|
+
## Features
|
10
|
+
|
11
|
+
- **Directory and File Tracking**: Handles both directories and files, including recursive directory structures.
|
12
|
+
- **Revision Tracking**: Computes and tracks revisions using MD5 hashes.
|
13
|
+
- **Change Detection**: Identifies and marks changes such as additions, deletions, and modifications.
|
14
|
+
- **Serialization**: Supports serialization to and from JSON format for easy storage and transfer.
|
15
|
+
- **Pretty Printing**: Provides a method to print the directory tree with statuses.
|
16
|
+
- **Iterate with `for_each`**: Traverse the tree and apply a block to files matching specific statuses.
|
17
|
+
|
18
|
+
## Install
|
19
|
+
|
20
|
+
The supported tools are:
|
21
|
+
- gitpack
|
22
|
+
- make
|
23
|
+
- gem
|
24
|
+
|
25
|
+
### gitpack
|
26
|
+
```sh
|
27
|
+
gitpack add juliankahlert/revtree
|
28
|
+
```
|
29
|
+
|
30
|
+
### make
|
31
|
+
```sh
|
32
|
+
git clone https://github.com/juliankahlert/revtree.git
|
33
|
+
cd revtree
|
34
|
+
sudo make install
|
35
|
+
```
|
36
|
+
|
37
|
+
### gem (local)
|
38
|
+
```sh
|
39
|
+
git clone https://github.com/juliankahlert/revtree.git
|
40
|
+
cd revtree
|
41
|
+
gem build revtree.gemspec
|
42
|
+
sudo gem install --local revtree-0.1.0.gem
|
43
|
+
```
|
44
|
+
|
45
|
+
## API Documentation
|
46
|
+
|
47
|
+
### `RevTree.new(path, whitelist = nil)`
|
48
|
+
|
49
|
+
- **Parameters**:
|
50
|
+
- `path` (String): The path to the file or directory.
|
51
|
+
- `whitelist` (Array<String>): List of patterns to include in the tree.
|
52
|
+
|
53
|
+
### `#print_tree(indent = 0)`
|
54
|
+
|
55
|
+
- **Parameters**:
|
56
|
+
- `indent` (Integer): Number of spaces to indent each level of the tree.
|
57
|
+
|
58
|
+
### `#to_h`
|
59
|
+
|
60
|
+
- **Returns**: A Hash representation of the RevTree object.
|
61
|
+
|
62
|
+
### `#to_json`
|
63
|
+
|
64
|
+
- **Returns**: A JSON representation of the RevTree object.
|
65
|
+
|
66
|
+
### `#for_each(status_whitelist, &block)`
|
67
|
+
|
68
|
+
- **Parameters**:
|
69
|
+
- `status_whitelist` (Array<Symbol>): List of statuses to include (e.g., `[:added, :removed]`).
|
70
|
+
- `&block` (Proc): A block to execute for each file matching the given statuses.
|
71
|
+
- **Behavior**: Iterates over files in the tree, executing the block for each file whose status matches one of the statuses in the whitelist.
|
72
|
+
|
73
|
+
### `RevTree.from_h(h)`
|
74
|
+
|
75
|
+
- **Parameters**:
|
76
|
+
- `h` (Hash): A hash representation of a RevTree object.
|
77
|
+
- **Returns**: A RevTree object.
|
78
|
+
|
79
|
+
### `RevTree.from_json(json_str)`
|
80
|
+
|
81
|
+
- **Parameters**:
|
82
|
+
- `json_str` (String): A JSON string representing a RevTree object.
|
83
|
+
- **Returns**: A RevTree object.
|
84
|
+
|
85
|
+
## Example Usage
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
#!/bin/env ruby
|
89
|
+
|
90
|
+
require 'revtree'
|
91
|
+
|
92
|
+
# Let's simulate two different directory structures to compare
|
93
|
+
file_tree_old = RevTree.new('./', ['*.rb', '*.md'])
|
94
|
+
file_tree_new = RevTree.new('./', ['*.rb'])
|
95
|
+
|
96
|
+
# Compare the two trees
|
97
|
+
result_tree = RevTree.compare(file_tree_old, file_tree_new)
|
98
|
+
|
99
|
+
# Print the resulting tree with change statuses
|
100
|
+
result_tree.print_tree
|
101
|
+
|
102
|
+
# Example of using for_each
|
103
|
+
result_tree.for_each([:added, :removed]) do |file, full_path|
|
104
|
+
p "File #{file.name} in #{full_path} was added/removed"
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
108
|
+
## Encouragement for Contribution
|
109
|
+
|
110
|
+
Vontributions from the community are welcome!
|
111
|
+
If you find any issues or have ideas for new features, please feel free to submit a pull request or open an issue.
|
112
|
+
Your input helps make RevTree better for everyone.
|
113
|
+
|
114
|
+
## License
|
115
|
+
|
116
|
+
RevTree is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
|
data/lib/revtree.rb
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
# lib/revtree.rb
|
2
|
+
#
|
3
|
+
# MIT License
|
4
|
+
#
|
5
|
+
# Copyright (c) 2024 Julian Kahlert
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
15
|
+
# copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
# SOFTWARE.
|
24
|
+
|
25
|
+
require 'digest'
|
26
|
+
require 'pathname'
|
27
|
+
require 'json'
|
28
|
+
|
29
|
+
class RevTree
|
30
|
+
attr_reader :children, :type, :name, :rev, :status
|
31
|
+
|
32
|
+
def initialize(path, whitelist = nil)
|
33
|
+
@path = Pathname.new(path)
|
34
|
+
@name = @path.basename.to_s
|
35
|
+
@whitelist = whitelist || []
|
36
|
+
@status = :unmodified
|
37
|
+
|
38
|
+
if @path.directory?
|
39
|
+
init_dir
|
40
|
+
else
|
41
|
+
init_file
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def calculate_file_rev
|
46
|
+
Digest::MD5.file(@path).hexdigest
|
47
|
+
end
|
48
|
+
|
49
|
+
def calculate_directory_rev
|
50
|
+
Digest::MD5.hexdigest(@children.map(&:rev).join)
|
51
|
+
end
|
52
|
+
|
53
|
+
def print_tree(indent = 0)
|
54
|
+
status_str = @status ? " (status: #{@status})" : ''
|
55
|
+
puts "#{' ' * indent}#{@type == :folder ? '[Folder]' : '[File]'} #{@name} (rev: #{@rev})#{status_str}"
|
56
|
+
@children.each { |child| child.print_tree(indent + 1) }
|
57
|
+
end
|
58
|
+
|
59
|
+
# Serialize the RevTree object to JSON
|
60
|
+
def to_h
|
61
|
+
{
|
62
|
+
type: @type,
|
63
|
+
name: @name,
|
64
|
+
rev: @rev,
|
65
|
+
status: @status,
|
66
|
+
children: @children.map(&:to_h),
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_json(*_args)
|
71
|
+
JSON.pretty_generate(self.to_h)
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.from_h(h)
|
75
|
+
new_tree(h[:name], h[:type].to_sym, h[:rev], h[:children], h[:status])
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.from_json(json_str)
|
79
|
+
data = JSON.parse(json_str, symbolize_names: true)
|
80
|
+
file_tree = from_h(data)
|
81
|
+
|
82
|
+
file_tree
|
83
|
+
end
|
84
|
+
|
85
|
+
def for_each(status_whitelist, &block)
|
86
|
+
return unless block_given?
|
87
|
+
|
88
|
+
RevTree.traverse_tree(self, status_whitelist, @path, &block)
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def init_dir
|
94
|
+
@type = :folder
|
95
|
+
@children = @path.children
|
96
|
+
.select { |c| include_in_tree?(c) }
|
97
|
+
.map { |c| RevTree.new(c, @whitelist) }
|
98
|
+
@rev = calculate_directory_rev
|
99
|
+
end
|
100
|
+
|
101
|
+
def init_file
|
102
|
+
@type = :file
|
103
|
+
@children = []
|
104
|
+
@rev = calculate_file_rev
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.new_tree(name, type, rev, children, status = :unmodified)
|
108
|
+
tree = allocate
|
109
|
+
tree.instance_variable_set(:@name, name)
|
110
|
+
tree.instance_variable_set(:@type, type.to_sym)
|
111
|
+
tree.instance_variable_set(:@rev, rev)
|
112
|
+
tree.instance_variable_set(:@status, status.to_sym)
|
113
|
+
if type == :folder && children
|
114
|
+
tree.instance_variable_set(:@children, children.map { |c| from_h(c) })
|
115
|
+
else
|
116
|
+
tree.instance_variable_set(:@children, [])
|
117
|
+
end
|
118
|
+
tree
|
119
|
+
end
|
120
|
+
|
121
|
+
def include_in_tree?(path)
|
122
|
+
return false if path.directory? && path.basename.to_s.start_with?('.')
|
123
|
+
|
124
|
+
return true if path.directory?
|
125
|
+
return true if @whitelist.empty?
|
126
|
+
|
127
|
+
@whitelist.any? { |p| File.fnmatch?(p, path.basename.to_s) }
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.compare(old, new)
|
131
|
+
return nil if old.nil? && new.nil?
|
132
|
+
|
133
|
+
return handle_addition(new) if old.nil?
|
134
|
+
return handle_removal(old) if new.nil?
|
135
|
+
|
136
|
+
if old.rev != new.rev
|
137
|
+
return handle_modification(old, new)
|
138
|
+
else
|
139
|
+
return handle_unmodified(old, new)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.handle_addition(new)
|
144
|
+
with_status = new.dup
|
145
|
+
with_status.instance_variable_set(:@status, :added)
|
146
|
+
with_status
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.handle_removal(old)
|
150
|
+
with_status = old.dup
|
151
|
+
with_status.instance_variable_set(:@status, :removed)
|
152
|
+
with_status
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.handle_modification(old, new)
|
156
|
+
if old.type == :folder && new.type == :folder
|
157
|
+
compare_folders(old, new, :modified)
|
158
|
+
else
|
159
|
+
with_status = new.dup
|
160
|
+
with_status.instance_variable_set(:@status, :modified)
|
161
|
+
with_status
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def self.compare_folders(old, new, status)
|
166
|
+
combined_children = merge_children(old.children, new.children)
|
167
|
+
with_status = new.dup
|
168
|
+
merged = combined_children.map { |o, n| compare(o, n) }
|
169
|
+
|
170
|
+
folder_status = merged.any? { |child| child.status == :modified } ? :modified : status
|
171
|
+
|
172
|
+
with_status.instance_variable_set(:@children, merged)
|
173
|
+
with_status.instance_variable_set(:@status, folder_status)
|
174
|
+
with_status
|
175
|
+
end
|
176
|
+
|
177
|
+
def self.handle_unmodified(old, new)
|
178
|
+
if old.type == :folder && new.type == :folder
|
179
|
+
compare_folders(old, new, :unmodified)
|
180
|
+
else
|
181
|
+
with_status = new.dup
|
182
|
+
with_status.instance_variable_set(:@status, :unmodified)
|
183
|
+
with_status
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def self.merge_children(old_children, new_children)
|
188
|
+
all_names = (old_children.map(&:name) + new_children.map(&:name)).uniq
|
189
|
+
all_names.map do |name|
|
190
|
+
old_child = old_children.find { |child| child.name == name }
|
191
|
+
new_child = new_children.find { |child| child.name == name }
|
192
|
+
[old_child, new_child]
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def self.traverse_tree(node, status_whitelist, current_path, &block)
|
197
|
+
if node.type == :file && status_whitelist.include?(node.status)
|
198
|
+
block.call(node, File.expand_path(current_path.to_s))
|
199
|
+
end
|
200
|
+
|
201
|
+
full_path = File.join(current_path.to_s, node.name.to_s)
|
202
|
+
|
203
|
+
node.children.each do |child|
|
204
|
+
traverse_tree(child, status_whitelist, full_path, &block)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: revtree
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Julian Kahlert
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-09-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3.4'
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '3.4'
|
33
|
+
description: RevTree builds a recursive directory tree and compares file revisions.
|
34
|
+
It can mark files and folders as added, removed, modified, or unmodified.
|
35
|
+
email:
|
36
|
+
- 90937526+juliankahlert@users.noreply.github.com
|
37
|
+
executables: []
|
38
|
+
extensions: []
|
39
|
+
extra_rdoc_files: []
|
40
|
+
files:
|
41
|
+
- README.md
|
42
|
+
- lib/revtree.rb
|
43
|
+
homepage: https://github.com/juliankahlert/revtree
|
44
|
+
licenses:
|
45
|
+
- MIT
|
46
|
+
metadata: {}
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 3.0.0
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
requirements: []
|
62
|
+
rubygems_version: 3.2.33
|
63
|
+
signing_key:
|
64
|
+
specification_version: 4
|
65
|
+
summary: A tool to build and compare file trees with revisions.
|
66
|
+
test_files: []
|