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.
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: []