git-status-tree 1.0.1 → 3.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ee588f3827fbbc7ce9a9d78bc8b4a3452d17bfc5a9426abe4246c617f52e9ee3
4
- data.tar.gz: 45bff253438b38d1464566fe9540b5d91f75505bee41575e4f26b106b4a48c47
3
+ metadata.gz: 261d0c82a5a74d301f58b8524456d4c2fbe29feac0f120e459f2ebd154a0ca57
4
+ data.tar.gz: dbe0de28748947459d8a6d1be0a535f69c7c72abb445565a4d05d667d99925dd
5
5
  SHA512:
6
- metadata.gz: f09d66715e49aff087232e2b1b770935a595647753d5f8dcdfa72813b854885661fd25f89e47b201802dbf75dfb06b95de0a15f184847605b12717b4b202b606
7
- data.tar.gz: b45b6f3aa2884014934795361670401b1b98027ae98076049c135f8267e57c4f139dfe574039f2d77cfabc067c770467514fb2ece6a95a09829fbd43fbdb54a3
6
+ metadata.gz: f0283802e22db622c566a797487849add4b5d08d607e8bed7bdc406be7da782f353a464894e0b45f62434c78d314785e98e674cb0bbc56b3dba3087250ce200e
7
+ data.tar.gz: 1282ceceb1e13cfacec5292c9c705b4c2ad2981559bf705b6dc3d1513987b2d54c8a4c3d85142df870c245ebedd290f69c2ca7a1f9dfe07a63754cbe1eaf05af
data/bin/git-status-tree CHANGED
@@ -1,6 +1,30 @@
1
1
  #!/usr/bin/env ruby
2
- # encoding: utf-8
2
+ # frozen_string_literal: true
3
3
 
4
- require File.join File.dirname(__FILE__), '../src/git-status-tree'
4
+ require 'optparse'
5
+ require File.join File.dirname(__FILE__), '../src/git_status_tree'
6
+ require File.join File.dirname(__FILE__), '../lib/version'
5
7
 
6
- puts GitStatusTree.new.to_s
8
+ parser = OptionParser.new do |opts|
9
+ opts.banner = 'Usage: git-status-tree [options]'
10
+
11
+ opts.on('-v', '--version', 'Show version') do
12
+ puts "git-status-tree #{GitStatusTree::VERSION}"
13
+ exit 0
14
+ end
15
+
16
+ opts.on('-h', '--help', 'Show this help message') do
17
+ puts opts
18
+ exit 0
19
+ end
20
+ end
21
+
22
+ begin
23
+ parser.parse!
24
+ rescue OptionParser::InvalidOption => e
25
+ puts "Error: #{e.message}"
26
+ puts parser
27
+ exit 1
28
+ end
29
+
30
+ puts GitStatusTree.new
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'mkmf'
2
4
 
3
5
  find_executable('bash')
@@ -5,11 +7,11 @@ find_executable('git')
5
7
  find_executable('make')
6
8
 
7
9
  # Trick Rubygems into thinking the generated Makefile was executed
8
- compile = File.join(Dir.pwd, 'git_tree.' + RbConfig::CONFIG['DLEXT'])
9
- File.open(compile, "w") {}
10
+ compile = File.join(Dir.pwd, "git_tree.#{RbConfig::CONFIG['DLEXT']}")
11
+ File.open(compile, 'w') {}
10
12
 
11
13
  # Install "git tree"
12
14
  puts `../../bin/git_add_alias_tree`
13
15
 
14
16
  # Trick Rubygems into thinking the Makefile was executed
15
- $makefile_created = true
17
+ $makefile_created = true # rubocop:disable Style/GlobalVars
data/lib/bash_color.rb CHANGED
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module BashColor
4
4
  NONE = "\033[0m"
data/lib/node.rb CHANGED
@@ -1,208 +1,223 @@
1
- # encoding: utf-8
2
-
3
- class NodeNameError < StandardError; end
4
- class NodeChildrenError < StandardError; end
5
- class NodeTypeError < StandardError; end
6
-
7
- # A Node represents a file or directory in the git-status-tree
8
- class Node
9
- class << self
10
- attr_accessor :indent
11
- end
12
-
13
- attr_accessor :status, :name, :children
14
-
15
- def initialize(name, children = nil, status = nil)
16
- self.class.indent ||= 4
17
- msg = '"name" must be a String.'
18
- raise NodeNameError, msg unless name.is_a? String
19
- msg = '"name" must have at least one character.'
20
- raise NodeNameError, msg if name.empty?
21
- msg = '"name" must not contain "/", use create_from_string.'
22
- raise NodeNameError, msg if name =~ /\//
23
- msg = '"children" must be a NodesCollection or nil.'
24
- valid_nodes_collection = children.nil? || children.is_a?(NodesCollection)
25
- raise NodeChildrenError, msg unless valid_nodes_collection
26
-
27
- @name = name
28
- @children = children
29
- @status = status || '??'
30
- end
31
-
32
- def self.create_from_string(gs_porcelain)
33
- msg = '"str_node" must be String.'
34
- raise NodeTypeError, msg unless gs_porcelain.is_a? String
35
- raise NodeNameError, '"str_node" too short.' if gs_porcelain.length < 4
36
- status = if gs_porcelain[1..1] == ' '
37
- gs_porcelain[0..0] + '+'
38
- else
39
- gs_porcelain[1..1]
40
- end
41
- path = './' + gs_porcelain[3..-1]
42
- ary_nodes = path.split(/\//)
43
- name = ary_nodes.shift
44
-
45
- if ary_nodes.any?
46
- children = NodesCollection.create_from_array(ary_nodes, status)
47
- node = self.new(name, children)
48
- else
49
- node = self.new(name, nil, status)
50
- end
51
-
52
- node
53
- end
54
-
55
- def self.instances?
56
- lambda { |node| node.is_a?(Node) }
57
- end
58
-
59
- def to_primitive
60
- if dir?
61
- {name => children.to_primitive}
62
- else
63
- name
64
- end
65
- end
66
-
67
- def file?
68
- children.nil?
69
- end
70
-
71
- def dir?
72
- !file?
73
- end
74
-
75
- def valid?
76
- return valid_dir? if dir?
77
- return valid_file? if file?
78
- false
79
- end
80
-
81
- # @return [NodesCollection]
82
- def +(other)
83
- raise 'not valid' unless self.valid? && other.valid?
84
- raise 'not a ' + self.class.to_s unless other.is_a?(self.class)
85
-
86
- tmp_children = [self.children, other.children].compact.inject(&:+)
87
-
88
- NodesCollection.new([self.class.new(self.name, tmp_children)])
89
- end
90
-
91
- def <=>(other)
92
- return (self.name <=> other.name) if self.file? == other.file?
93
- return -1 if (self.dir? && other.file?)
94
- return 1 if (self.file? && other.dir?)
95
- 0
96
- end
97
-
98
- def to_tree_s(depth = 0, open_parents = [0], last = true)
99
- open_parents << depth
100
-
101
- pre = pre_tree(depth, open_parents, last)
102
-
103
- color_name = ''
104
- if dir?
105
- color_name += BashColor::EMB + name
106
- else #file?
107
- if staged?
108
- color_name += BashColor::G
109
- else
110
- color_name += BashColor::R
111
- end
112
- color_name += name + ' (' + status + ')'
113
- end
114
- color_name += BashColor::NONE
115
-
116
-
117
- str_tree = pre + color_name + "\n"
118
- str_tree << children.to_tree_s(depth + 1, open_parents) if children
119
-
120
- str_tree
121
- end
122
-
123
- # 'M' modified
124
- def modified?
125
- self.status.include?('M')
126
- end
127
-
128
- # 'A' added
129
- def added?
130
- self.status.include?('A')
131
- end
132
-
133
- # 'D' deleted
134
- def deleted?
135
- self.status.include?('D')
136
- end
137
-
138
- # 'R' renamed
139
- def renamed?
140
- self.status.include?('R')
141
- end
142
-
143
- # 'C' copied
144
- def copied?
145
- self.status.include?('C')
146
- end
147
-
148
- # 'U' updated but unmerged
149
- def unmerged?
150
- self.status.include?('U')
151
- end
152
-
153
- # '?' new
154
- def new?
155
- self.status.include?('?')
156
- end
157
-
158
- # '+' staged
159
- def staged?
160
- self.status.include?('+')
161
- end
162
-
163
- private
164
- # Has a valid name?
165
- def name_valid?
166
- name &&
167
- name.is_a?(String) &&
168
- (name.length > 0) &&
169
- name.match(/\//).nil?
170
- end
171
-
172
- # Is a valid dir?
173
- def valid_dir?
174
- name_valid? &&
175
- children.is_a?(NodesCollection) &&
176
- children.valid?
177
- end
178
-
179
- # Is a valid file?
180
- def valid_file?
181
- name_valid? &&
182
- children.nil?
183
- end
184
-
185
- def pre_tree(depth, open_parents, last)
186
- if depth == 0
187
- ''
188
- elsif depth > 0
189
- pre_ary = Array.new(depth).fill(' ')
190
-
191
- indent = self.class.indent - 2
192
- open_parents.each do |idx|
193
- pre_ary[idx] = '│' + (' ' * indent) + ' ' if pre_ary[idx] == ' '
194
- end
195
-
196
- if last
197
- pre_ary[-1] = '└'
198
- open_parents.delete(depth-1)
199
- else
200
- pre_ary[-1] = '├'
201
- end
202
- pre_ary[-1] += ('─' * indent) + ' '
203
-
204
-
205
- pre_ary * ''
206
- end
207
- end
208
- end
1
+ # frozen_string_literal: true
2
+
3
+ class NodeNameError < StandardError; end
4
+ class NodeChildrenError < StandardError; end
5
+ class NodeTypeError < StandardError; end
6
+
7
+ # A Node represents a file or directory in the git-status-tree
8
+ class Node # rubocop:disable Metrics/ClassLength
9
+ class << self
10
+ attr_accessor :indent
11
+ end
12
+
13
+ attr_accessor :status, :name, :children
14
+
15
+ def initialize(name, children = nil, status = nil)
16
+ self.class.indent ||= 4
17
+ validate_name!(name)
18
+
19
+ msg = '"children" must be a NodesCollection or nil.'
20
+ valid_nodes_collection = children.nil? || children.is_a?(NodesCollection)
21
+ raise NodeChildrenError, msg unless valid_nodes_collection
22
+
23
+ @name = name
24
+ @children = children
25
+ @status = status || '??'
26
+ end
27
+
28
+ def self.create_from_string(gs_porcelain)
29
+ msg = '"str_node" must be String.'
30
+ raise NodeTypeError, msg unless gs_porcelain.is_a? String
31
+ raise NodeNameError, '"str_node" too short.' if gs_porcelain.length < 4
32
+
33
+ node_from_gs(gs_porcelain)
34
+ end
35
+
36
+ def self.instances?
37
+ ->(node) { node.is_a?(Node) }
38
+ end
39
+
40
+ def self.node_from_gs(gs_porcelain)
41
+ status = status(gs_porcelain)
42
+ ary_nodes = "./#{gs_porcelain[3..]}".split(%r{/})
43
+
44
+ name = ary_nodes.shift
45
+ if ary_nodes.any?
46
+ children = NodesCollection.create_from_array(ary_nodes, status)
47
+ new(name, children)
48
+ else
49
+ new(name, nil, status)
50
+ end
51
+ end
52
+
53
+ private_class_method :node_from_gs
54
+
55
+ def self.status(gs_porcelain)
56
+ status = "#{gs_porcelain[0]}+" if gs_porcelain[1] == ' '
57
+ status = gs_porcelain[1] unless gs_porcelain[1] == ' '
58
+ status
59
+ end
60
+ private_class_method :status
61
+
62
+ def to_primitive
63
+ if dir?
64
+ { name => children.to_primitive }
65
+ else
66
+ name
67
+ end
68
+ end
69
+
70
+ def file?
71
+ children.nil?
72
+ end
73
+
74
+ def dir?
75
+ !file?
76
+ end
77
+
78
+ def valid?
79
+ return valid_dir? if dir?
80
+ return valid_file? if file?
81
+
82
+ false
83
+ end
84
+
85
+ # @return [NodesCollection]
86
+ def +(other)
87
+ raise 'not valid' unless valid? && other.valid?
88
+ raise "not a #{self.class}" unless other.is_a?(self.class)
89
+
90
+ tmp_children = [children, other.children].compact.inject(&:+)
91
+
92
+ NodesCollection.new([self.class.new(name, tmp_children)])
93
+ end
94
+
95
+ def <=>(other)
96
+ return (name <=> other.name) if file? == other.file?
97
+ return -1 if dir? && other.file?
98
+ return 1 if file? && other.dir?
99
+
100
+ 0
101
+ end
102
+
103
+ def to_tree_s(depth = 0, open_parents = [0], last: true)
104
+ open_parents << depth
105
+
106
+ pre = pre_tree(depth, open_parents, last)
107
+
108
+ str_tree = "#{pre}#{color_name}\n"
109
+ str_tree += children.to_tree_s(depth + 1, open_parents) if children
110
+
111
+ str_tree
112
+ end
113
+
114
+ # 'M' modified
115
+ def modified?
116
+ status.include?('M')
117
+ end
118
+
119
+ # 'A' added
120
+ def added?
121
+ status.include?('A')
122
+ end
123
+
124
+ # 'D' deleted
125
+ def deleted?
126
+ status.include?('D')
127
+ end
128
+
129
+ # 'R' renamed
130
+ def renamed?
131
+ status.include?('R')
132
+ end
133
+
134
+ # 'C' copied
135
+ def copied?
136
+ status.include?('C')
137
+ end
138
+
139
+ # 'U' updated but unmerged
140
+ def unmerged?
141
+ status.include?('U')
142
+ end
143
+
144
+ # '?' new
145
+ def new?
146
+ status.include?('?')
147
+ end
148
+
149
+ # '+' staged
150
+ def staged?
151
+ status.include?('+')
152
+ end
153
+
154
+ private
155
+
156
+ def validate_name!(name)
157
+ msg = '"name" must be a String.'
158
+ raise NodeNameError, msg unless name.is_a? String
159
+
160
+ msg = '"name" must have at least one character.'
161
+ raise NodeNameError, msg if name.empty?
162
+
163
+ msg = '"name" must not contain "/", use create_from_string.'
164
+ raise NodeNameError, msg if name =~ %r{/}
165
+ end
166
+
167
+ def color_name
168
+ color_name = ''
169
+ if dir?
170
+ color_name += BashColor::EMB + name
171
+ else
172
+ color_name += BashColor::G if staged?
173
+ color_name += BashColor::R unless staged?
174
+ color_name += "#{name} (#{status})"
175
+ end
176
+ color_name + BashColor::NONE
177
+ end
178
+
179
+ # Has a valid name?
180
+ def name_valid?
181
+ name.is_a?(String) &&
182
+ name.length.positive? &&
183
+ name.match(%r{/}).nil?
184
+ end
185
+
186
+ # Is a valid dir?
187
+ def valid_dir?
188
+ name_valid? &&
189
+ children.is_a?(NodesCollection) &&
190
+ children.valid?
191
+ end
192
+
193
+ # Is a valid file?
194
+ def valid_file?
195
+ name_valid? &&
196
+ children.nil?
197
+ end
198
+
199
+ def pre_tree(depth, open_parents, last)
200
+ if depth.zero?
201
+ ''
202
+ elsif depth.positive?
203
+ pre_ary = Array.new(depth).fill(' ')
204
+ indent = self.class.indent - 2
205
+
206
+ open_parents.each { |idx| pre_ary[idx] = "│#{' ' * indent} " if pre_ary[idx] == ' ' }
207
+
208
+ sibling(depth, indent, last, open_parents, pre_ary)
209
+
210
+ pre_ary * ''
211
+ end
212
+ end
213
+
214
+ def sibling(depth, indent, last, open_parents, pre_ary)
215
+ if last
216
+ pre_ary[-1] = '└'
217
+ open_parents.delete(depth - 1)
218
+ else
219
+ pre_ary[-1] = '├'
220
+ end
221
+ pre_ary[-1] += "#{'─' * indent} "
222
+ end
223
+ end
@@ -1,179 +1,194 @@
1
- # encoding: utf-8
2
-
3
- # error class for invalid NodeCollection types
4
- class NodesCollectionTypeError < StandardError;
5
- end
6
-
7
- # collection of nodes
8
- class NodesCollection
9
- attr_accessor :nodes
10
-
11
- def self.create_from_string(str_nodes, status = ' ')
12
- msg = '"str_nodes" must be String.'
13
- raise NodesCollectionTypeError, msg unless str_nodes.is_a? String
14
-
15
- ary_nodes = str_nodes.split(/\//)
16
- create_from_valid_array(ary_nodes, status)
17
- end
18
-
19
- def self.create_from_array(ary_nodes, status)
20
- ary_nodes = [ary_nodes].flatten(1)
21
-
22
- msg = '"ary_nodes" must only contain Strings.'
23
- are_strings = lambda { |node| node.is_a?(String) }
24
- raise NodesCollectionTypeError, msg unless ary_nodes.all? &are_strings
25
-
26
- create_from_valid_array(ary_nodes, status)
27
- end
28
-
29
- def self.new_from_nodes_array(nodes)
30
- raise msg unless nodes.all? &Node.instances?
31
-
32
- all_nodes = nodes.group_by(&:name)
33
-
34
- plain_nodes = []
35
- all_nodes.each { |_, nodes| plain_nodes << nodes if nodes.length == 1 }
36
- plain_nodes.flatten!(1)
37
-
38
- merged_nodes = []
39
- all_nodes.each do |_, nodes|
40
- if nodes.length == 2
41
- merged_nodes << (nodes[0] + nodes[1]).nodes[0]
42
- end
43
- end
44
-
45
- self.new(plain_nodes + merged_nodes)
46
- end
47
-
48
- def self.create_from_valid_array(ary_nodes, status)
49
- name = ary_nodes.shift
50
- node = if ary_nodes.any?
51
- children = create_from_valid_array(ary_nodes, status)
52
- Node.new(name, children)
53
- else
54
- Node.new(name, nil, status)
55
- end
56
- self.new([node])
57
- end
58
-
59
- private_class_method :create_from_valid_array
60
-
61
- def initialize(nodes = [])
62
- nodes = [nodes].flatten(1)
63
-
64
- msg = '"nodes" must only contain Nodes.'
65
- are_nodes = lambda { |node| node.is_a?(Node) }
66
- raise NodesCollectionTypeError, msg unless nodes.all? &are_nodes
67
- @nodes = nodes
68
- end
69
-
70
- # @return [NodesCollection]
71
- def +(other)
72
- unless other.is_a?(Node) || other.is_a?(self.class)
73
- raise 'not a Node or NodesCollection'
74
- end
75
-
76
- all_nodes = merge_nodes_with other
77
- all_dirs = all_nodes.select(&:dir?)
78
- all_files = all_nodes.select(&:file?)
79
-
80
- dirs_collection = self.class.new_from_nodes_array all_dirs
81
- files_collection = self.class.new all_files
82
-
83
-
84
- dir_nodes = dirs_collection.sort!
85
- file_nodes = files_collection.sort!
86
-
87
- self.class.new(dir_nodes + file_nodes)
88
- end
89
-
90
- # @return [Integer]
91
- def <=>(other)
92
- self.to_primitive <=> other.to_primitive
93
- end
94
-
95
- # @return [Array<Node>]
96
- def nodes_not_in(other)
97
- self_names = self.nodes.map(&:name)
98
- other_names = other.nodes.map(&:name)
99
-
100
- self_only_names = self_names - (self_names & other_names)
101
-
102
- self.nodes.select { |node| self_only_names.include?(node.name) }
103
- end
104
-
105
- # @return [Array<Node>]
106
- def merge_common_nodes_with(other)
107
- self_names = self.nodes.map(&:name)
108
- other_names = other.nodes.map(&:name)
109
-
110
- common_names = self_names & other_names
111
- all_nodes = self.nodes + other.nodes
112
-
113
- common_names.map do |name|
114
- all_nodes.select { |node| node.name == name }.reduce(&:+).nodes[0]
115
- end
116
- end
117
-
118
- # @return [Array<Node>]
119
- def merge_nodes_with(other)
120
- if other.is_a? Node
121
- if self.nodes.map(&:name).include?(other.name)
122
- equal_names = lambda { |node| node.name == other.name }
123
- collection_merged = self.nodes.select(&equal_names)[0] + other
124
- other = collection_merged.nodes[0]
125
- end
126
-
127
- nodes_merged = self.nodes + [other]
128
- elsif other.is_a? NodesCollection
129
- self_dedicated_nodes = self.nodes_not_in other
130
- other_dedicated_nodes = other.nodes_not_in self
131
- common_nodes = self.merge_common_nodes_with other
132
-
133
- nodes_merged = self_dedicated_nodes + common_nodes + other_dedicated_nodes
134
- end
135
-
136
- nodes_merged
137
- end
138
-
139
- def files
140
- nodes.select(&:file?)
141
- end
142
-
143
- def dirs
144
- nodes.select(&:dir?)
145
- end
146
-
147
- def sort!
148
- nodes.sort!
149
- end
150
-
151
- def to_primitive
152
- nodes.map(&:to_primitive)
153
- end
154
-
155
- def valid?
156
- nodes &&
157
- nodes.is_a?(Array) &&
158
- nodes.all? { |node| node.is_a?(Node) } &&
159
- nodes.all?(&:valid?)
160
- #TODO compare uniqueness of file and dir names.
161
- end
162
-
163
- def to_tree_s(depth = 0, open_parents = [])
164
- tree_s = ''
165
-
166
- if nodes.length > 1
167
- to_tree_s = lambda { |node| node.to_tree_s(depth, open_parents, false) }
168
- tree_s << nodes[0..-2].map(&to_tree_s) * ''
169
- end
170
- tree_s << nodes.last.to_tree_s(depth, open_parents)
171
-
172
- tree_s
173
- end
174
-
175
- private
176
- def add_node(other)
177
- end
178
-
179
- end
1
+ # frozen_string_literal: true
2
+
3
+ # error class for invalid NodeCollection types
4
+ class NodesCollectionTypeError < StandardError
5
+ end
6
+
7
+ # collection of nodes
8
+ class NodesCollection # rubocop:disable Metrics/ClassLength
9
+ attr_accessor :nodes
10
+
11
+ def self.create_from_string(str_nodes, status = ' ')
12
+ msg = '"str_nodes" must be String.'
13
+ raise NodesCollectionTypeError, msg unless str_nodes.is_a? String
14
+
15
+ ary_nodes = str_nodes.split(%r{/})
16
+ create_from_valid_array(ary_nodes, status)
17
+ end
18
+
19
+ def self.create_from_array(ary_nodes, status)
20
+ ary_nodes = [ary_nodes].flatten(1)
21
+
22
+ msg = '"ary_nodes" must only contain Strings.'
23
+ are_strings = ->(node) { node.is_a?(String) }
24
+ raise NodesCollectionTypeError, msg unless ary_nodes.all?(&are_strings)
25
+
26
+ create_from_valid_array(ary_nodes, status)
27
+ end
28
+
29
+ def self.new_from_nodes_array(all_nodes)
30
+ raise msg unless all_nodes.all?(&Node.instances?)
31
+
32
+ grouped_nodes = all_nodes.group_by(&:name)
33
+ plain_nodes = plain_nodes(grouped_nodes)
34
+ merged_nodes = merged_nodes(grouped_nodes)
35
+
36
+ new(plain_nodes + merged_nodes)
37
+ end
38
+
39
+ def self.create_from_valid_array(ary_nodes, status)
40
+ name = ary_nodes.shift
41
+ node = if ary_nodes.any?
42
+ children = create_from_valid_array(ary_nodes, status)
43
+ Node.new(name, children)
44
+ else
45
+ Node.new(name, nil, status)
46
+ end
47
+ new([node])
48
+ end
49
+
50
+ private_class_method :create_from_valid_array
51
+
52
+ def self.merged_nodes(grouped_nodes)
53
+ merged_nodes = []
54
+ grouped_nodes.each_value do |nodes|
55
+ merged_nodes << (nodes[0] + nodes[1]).nodes[0] if nodes.length == 2
56
+ end
57
+ merged_nodes
58
+ end
59
+ private_class_method :merged_nodes
60
+
61
+ def self.plain_nodes(grouped_nodes)
62
+ grouped_nodes.filter { |_, nodes| nodes.length == 1 }.values.flatten(1)
63
+ end
64
+ private_class_method :plain_nodes
65
+
66
+ def initialize(nodes = [])
67
+ nodes = [nodes].flatten(1)
68
+
69
+ msg = '"nodes" must only contain Nodes.'
70
+ are_nodes = ->(node) { node.is_a?(Node) }
71
+ raise NodesCollectionTypeError, msg unless nodes.all?(&are_nodes)
72
+
73
+ @nodes = nodes
74
+ end
75
+
76
+ # @return [NodesCollection]
77
+ def +(other)
78
+ raise 'not a Node or NodesCollection' unless other.is_a?(Node) || other.is_a?(self.class)
79
+
80
+ all_nodes = merge_nodes_with other
81
+
82
+ dir_nodes = dir_nodes(all_nodes)
83
+ file_nodes = file_nodes(all_nodes)
84
+
85
+ self.class.new(dir_nodes + file_nodes)
86
+ end
87
+
88
+ # @return [Integer]
89
+ def <=>(other)
90
+ to_primitive <=> other.to_primitive
91
+ end
92
+
93
+ # @return [Array<Node>]
94
+ def nodes_not_in(other)
95
+ self_names = nodes.map(&:name)
96
+ other_names = other.nodes.map(&:name)
97
+
98
+ self_only_names = self_names - (self_names & other_names)
99
+
100
+ nodes.select { |node| self_only_names.include?(node.name) }
101
+ end
102
+
103
+ # @return [Array<Node>]
104
+ def merge_common_nodes_with(other)
105
+ self_names = nodes.map(&:name)
106
+ other_names = other.nodes.map(&:name)
107
+
108
+ common_names = self_names & other_names
109
+ all_nodes = nodes + other.nodes
110
+
111
+ common_names.map do |name|
112
+ all_nodes.select { |node| node.name == name }.reduce(&:+).nodes[0]
113
+ end
114
+ end
115
+
116
+ # @return [Array<Node>]
117
+ def merge_nodes_with(other)
118
+ case other
119
+ when Node
120
+ nodes_merged = merge_nodes_with_node(other)
121
+ when NodesCollection
122
+ nodes_merged = merge_nodes_with_collection(other)
123
+ end
124
+
125
+ nodes_merged
126
+ end
127
+
128
+ def files
129
+ nodes.select(&:file?)
130
+ end
131
+
132
+ def dirs
133
+ nodes.select(&:dir?)
134
+ end
135
+
136
+ def sort!
137
+ nodes.sort!
138
+ end
139
+
140
+ def to_primitive
141
+ nodes.map(&:to_primitive)
142
+ end
143
+
144
+ def valid?
145
+ nodes.is_a?(Array) &&
146
+ nodes.all? { |node| node.is_a?(Node) } &&
147
+ nodes&.all?(&:valid?)
148
+ # TODO: compare uniqueness of file and dir names.
149
+ end
150
+
151
+ def to_tree_s(depth = 0, open_parents = [])
152
+ tree_s = ''
153
+
154
+ if nodes.length > 1
155
+ to_tree_s = ->(node) { node.to_tree_s(depth, open_parents, last: false) }
156
+ tree_s += nodes[0..-2].map(&to_tree_s) * ''
157
+ end
158
+ tree_s += nodes.last.to_tree_s(depth, open_parents)
159
+
160
+ tree_s
161
+ end
162
+
163
+ def file_nodes(all_nodes)
164
+ all_files = all_nodes.select(&:file?)
165
+ files_collection = self.class.new all_files
166
+ files_collection.sort!
167
+ end
168
+
169
+ def dir_nodes(all_nodes)
170
+ all_dirs = all_nodes.select(&:dir?)
171
+ dirs_collection = self.class.new_from_nodes_array all_dirs
172
+ dirs_collection.sort!
173
+ end
174
+
175
+ def merge_nodes_with_collection(other)
176
+ self_dedicated_nodes = nodes_not_in other
177
+ other_dedicated_nodes = other.nodes_not_in self
178
+ common_nodes = merge_common_nodes_with other
179
+
180
+ self_dedicated_nodes + common_nodes + other_dedicated_nodes
181
+ end
182
+
183
+ def merge_nodes_with_node(other)
184
+ if nodes.map(&:name).include?(other.name)
185
+ equal_names = ->(node) { node.name == other.name }
186
+ collection_merged = nodes.select(&equal_names)[0] + other
187
+ other = collection_merged.nodes[0]
188
+ end
189
+
190
+ nodes + [other]
191
+ end
192
+
193
+ def add_node(other); end
194
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Gem.pre_uninstall do |uninstaller|
2
4
  bin_dir = uninstaller.spec.bin_dir
3
5
  git_remove_alias_tree = File.join(bin_dir, 'git_remove_alias_tree')
data/lib/version.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Version information for git-status-tree
4
+ class GitStatusTree
5
+ VERSION_FILE = File.expand_path('../VERSION', __dir__)
6
+ VERSION = File.exist?(VERSION_FILE) ? File.read(VERSION_FILE).strip : 'unknown'
7
+ end
@@ -1,18 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.join File.dirname(__FILE__), '../lib/node'
4
+ require File.join File.dirname(__FILE__), '../lib/nodes_collection'
5
+ require File.join File.dirname(__FILE__), '../lib/bash_color'
6
+ require File.join File.dirname(__FILE__), '../src/git_status_tree'
7
+
1
8
  # GitStatusTree
2
9
  # use GitStatusTree.new.to_s to print the current git-status-tree
3
10
  class GitStatusTree
4
11
  attr_reader :files, :nodes, :tree
5
12
 
6
13
  def initialize(options = {})
7
- config = %x(git config --global status-tree.indent).strip
8
- config = (config =~ /\A\d+\z/) ? config.to_i : nil
9
- indent = options[:indent] || config || 4
10
- indent = 2 if indent < 2
11
- indent = 10 if indent > 10
12
- Node.indent = indent
13
- @files = (%x(git status --porcelain)).split(/\n/)
14
- @nodes = files.map{|file| Node.create_from_string file}
15
- @tree = nodes.reduce{|a, i| (a+i).nodes[0]}
14
+ Node.indent = indent(options)
15
+ @files = `git status --porcelain`.split(/\n/)
16
+ @nodes = files.map { |file| Node.create_from_string file }
17
+ @tree = nodes.reduce { |a, i| (a + i).nodes[0] }
16
18
  end
17
19
 
18
20
  def to_s
@@ -22,4 +24,18 @@ class GitStatusTree
22
24
  tree.to_tree_s
23
25
  end
24
26
  end
27
+
28
+ private
29
+
30
+ def indent(options)
31
+ indent = options[:indent] || config || 4
32
+ indent = 2 if indent < 2
33
+ indent = 10 if indent > 10
34
+ indent
35
+ end
36
+
37
+ def config
38
+ config = `git config --global status-tree.indent`.strip
39
+ config =~ /\A\d+\z/ ? config.to_i : nil
40
+ end
25
41
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git-status-tree
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wolfgang Teuber
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2021-10-07 00:00:00.000000000 Z
10
+ date: 2025-06-25 00:00:00.000000000 Z
12
11
  dependencies: []
13
12
  description: git-status-tree is a command line tool that shows git repository changes
14
13
  in a file tree.
@@ -28,7 +27,7 @@ files:
28
27
  - lib/node.rb
29
28
  - lib/nodes_collection.rb
30
29
  - lib/rubygems_plugin.rb
31
- - src/git-status-tree.rb
30
+ - lib/version.rb
32
31
  - src/git_status_tree.rb
33
32
  homepage: https://github.com/knugie/git-status-tree
34
33
  licenses:
@@ -36,7 +35,6 @@ licenses:
36
35
  - GPL-2.0
37
36
  metadata:
38
37
  source_code_uri: https://github.com/knugie/git-status-tree
39
- post_install_message:
40
38
  rdoc_options: []
41
39
  require_paths:
42
40
  - lib
@@ -44,15 +42,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
44
42
  requirements:
45
43
  - - ">="
46
44
  - !ruby/object:Gem::Version
47
- version: '0'
45
+ version: '3.3'
48
46
  required_rubygems_version: !ruby/object:Gem::Requirement
49
47
  requirements:
50
48
  - - ">="
51
49
  - !ruby/object:Gem::Version
52
50
  version: '0'
53
51
  requirements: []
54
- rubygems_version: 3.2.3
55
- signing_key:
52
+ rubygems_version: 3.6.2
56
53
  specification_version: 4
57
54
  summary: git status in file tree format
58
55
  test_files: []
@@ -1,16 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require File.join File.dirname(__FILE__), '../lib/node'
4
- require File.join File.dirname(__FILE__), '../lib/nodes_collection'
5
- require File.join File.dirname(__FILE__), '../lib/bash_color'
6
- require File.join File.dirname(__FILE__), '../src/git_status_tree'
7
-
8
- # GIT STATUS
9
- # ' ' unmodified
10
- # 'M' modified
11
- # 'A' added
12
- # 'D' deleted
13
- # 'R' renamed
14
- # 'C' copied
15
- # 'U' updated but unmerged
16
- # '??' other