revtree 0.1.0

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 +7 -0
  2. data/README.md +116 -0
  3. data/lib/revtree.rb +207 -0
  4. 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: []