term_utils 0.3.2 → 0.5.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 +4 -4
- data/CHANGELOG.md +16 -2
- data/COPYING +3 -3
- data/README.md +51 -16
- data/Rakefile +6 -0
- data/doc/TermUtils/AP/Article.html +57 -55
- data/doc/TermUtils/AP/ArticleResult.html +584 -0
- data/doc/TermUtils/AP/Flag.html +295 -78
- data/doc/TermUtils/AP/Parameter.html +891 -103
- data/doc/TermUtils/AP/ParameterResult.html +980 -0
- data/doc/TermUtils/{FF/Cursor/Context.html → AP/ParameterWalkerHooks.html} +60 -60
- data/doc/TermUtils/AP/ParseError.html +651 -19
- data/doc/TermUtils/AP/Parser.html +181 -121
- data/doc/TermUtils/AP/Result.html +201 -528
- data/doc/TermUtils/AP/Syntax.html +103 -393
- data/doc/TermUtils/AP/SyntaxError.html +9 -91
- data/doc/TermUtils/AP/Walker.html +686 -0
- data/doc/TermUtils/AP.html +49 -160
- data/doc/TermUtils/FF/Config.html +203 -35
- data/doc/TermUtils/FF/Context.html +585 -0
- data/doc/TermUtils/FF/Entry.html +626 -0
- data/doc/TermUtils/FF/Finder.html +850 -0
- data/doc/TermUtils/FF/{Cursor.html → FinderEntry.html} +473 -211
- data/doc/TermUtils/FF/FinderQuery.html +946 -0
- data/doc/TermUtils/FF/Query.html +402 -70
- data/doc/TermUtils/FF.html +135 -11
- data/doc/TermUtils/PropertyTreeNode.html +304 -190
- data/doc/TermUtils/Tab/Column.html +98 -96
- data/doc/TermUtils/Tab/Header.html +30 -30
- data/doc/TermUtils/Tab/Holder.html +81 -81
- data/doc/TermUtils/Tab/Printer.html +43 -43
- data/doc/TermUtils/Tab/Table.html +124 -128
- data/doc/TermUtils/Tab/TableError.html +7 -89
- data/doc/TermUtils/Tab.html +93 -86
- data/doc/TermUtils.html +10 -10
- data/doc/_index.html +62 -42
- data/doc/class_list.html +3 -3
- data/doc/css/style.css +3 -2
- data/doc/file.README.html +63 -26
- data/doc/file_list.html +2 -2
- data/doc/frames.html +2 -2
- data/doc/index.html +63 -26
- data/doc/js/app.js +14 -3
- data/doc/method_list.html +708 -236
- data/doc/top-level-namespace.html +7 -7
- data/lib/term_utils/ap/article.rb +15 -9
- data/lib/term_utils/ap/flag.rb +37 -20
- data/lib/term_utils/ap/parameter.rb +88 -19
- data/lib/term_utils/ap/parser.rb +143 -116
- data/lib/term_utils/ap/result.rb +208 -161
- data/lib/term_utils/ap/syntax.rb +53 -69
- data/lib/term_utils/ap.rb +79 -24
- data/lib/term_utils/ff/config.rb +22 -10
- data/lib/term_utils/{ap/no_such_value_error.rb → ff/entry.rb} +26 -8
- data/lib/term_utils/ff/finder.rb +255 -0
- data/lib/term_utils/ff/query.rb +94 -17
- data/lib/term_utils/ff.rb +12 -2
- data/lib/term_utils/property_tree_node.rb +47 -19
- data/lib/term_utils/tab.rb +106 -61
- data/lib/term_utils.rb +8 -1
- data/term_utils.gemspec +4 -4
- metadata +18 -17
- data/doc/TermUtils/AP/Element.html +0 -1025
- data/doc/TermUtils/AP/Level.html +0 -638
- data/doc/TermUtils/AP/NoSuchValueError.html +0 -217
- data/lib/term_utils/ap/element.rb +0 -78
- data/lib/term_utils/ap/level.rb +0 -57
- data/lib/term_utils/ap/parse_error.rb +0 -27
- data/lib/term_utils/ap/syntax_error.rb +0 -27
- data/lib/term_utils/ff/cursor.rb +0 -153
data/lib/term_utils/ap.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (C) 2023 Thomas Baron
|
2
4
|
#
|
3
5
|
# This file is part of term_utils.
|
4
6
|
#
|
@@ -13,21 +15,82 @@
|
|
13
15
|
#
|
14
16
|
# You should have received a copy of the GNU General Public License
|
15
17
|
# along with term_utils. If not, see <https://www.gnu.org/licenses/>.
|
16
|
-
|
17
|
-
require 'term_utils/ap/no_such_value_error'
|
18
|
-
require 'term_utils/ap/parse_error'
|
19
|
-
require 'term_utils/ap/syntax_error'
|
18
|
+
|
20
19
|
require 'term_utils/ap/article'
|
21
20
|
require 'term_utils/ap/flag'
|
22
|
-
require 'term_utils/ap/element'
|
23
|
-
require 'term_utils/ap/level'
|
24
21
|
require 'term_utils/ap/parameter'
|
25
22
|
require 'term_utils/ap/syntax'
|
26
23
|
require 'term_utils/ap/result'
|
27
24
|
require 'term_utils/ap/parser'
|
25
|
+
|
28
26
|
module TermUtils
|
29
|
-
# The Argument Parser module provides a way to parse
|
27
|
+
# The Argument Parser module provides a way to parse arguments.
|
30
28
|
module AP
|
29
|
+
# ParseError.
|
30
|
+
class ParseError < StandardError
|
31
|
+
# Constructs a new ParseError.
|
32
|
+
# @param props [Hash<Symbol, Object>]
|
33
|
+
def initialize(props = {})
|
34
|
+
super(self.class.create_message(props))
|
35
|
+
@props = props.dup
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns the properties associated to this one.
|
39
|
+
# @return [Hash<Symbol, Object>]
|
40
|
+
def props
|
41
|
+
@props.dup
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns the short message (i.e. the message without properties).
|
45
|
+
# @return [String] The short message.
|
46
|
+
def short_message
|
47
|
+
@props.fetch(:message)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Tests whether this one has a syntax parameter ID.
|
51
|
+
# @return [Boolean]
|
52
|
+
def parameter?
|
53
|
+
@props.key?(:parameter)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns the syntax parameter ID (if any).
|
57
|
+
# @return [Symbol, nil] The syntax parameter ID if any, `nil` otherwise.
|
58
|
+
def parameter
|
59
|
+
@props.fetch(:parameter, nil)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Tests whether this one has a faulty argument.
|
63
|
+
# @return [Boolean]
|
64
|
+
def fault?
|
65
|
+
@props.key?(:fault)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns the faulty argument (if any).
|
69
|
+
# @return [String, nil] The faulty argument if any, `nil` otherwise.
|
70
|
+
def fault
|
71
|
+
@props.fetch(:fault, nil)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Creates a message based on given properties.
|
75
|
+
# @param props [Hash<Symbol, Object>]
|
76
|
+
# @return [String]
|
77
|
+
def self.create_message(props)
|
78
|
+
props = props.dup
|
79
|
+
msg = props.delete(:message) { |key| raise StandardError, "#{key} property is mandatory" }
|
80
|
+
return msg if props.empty?
|
81
|
+
|
82
|
+
vals = []
|
83
|
+
props.each do |key, val|
|
84
|
+
vals << "#{key}: \"#{val}\""
|
85
|
+
end
|
86
|
+
"#{msg} (#{vals.join(', ')})"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# SyntaxError.
|
91
|
+
class SyntaxError < StandardError
|
92
|
+
end
|
93
|
+
|
31
94
|
# Creates a new Syntax.
|
32
95
|
# @return [TermUtils::AP::Syntax]
|
33
96
|
def self.create_syntax(&block)
|
@@ -35,25 +98,17 @@ module TermUtils
|
|
35
98
|
block.call(new_syntax) if block
|
36
99
|
new_syntax
|
37
100
|
end
|
38
|
-
|
39
|
-
# @param syntax [TermUtils::AP::Syntax]
|
40
|
-
# @param command [String]
|
41
|
-
# @param opts [Hash]
|
42
|
-
# @return [TermUtils::AP::Result]
|
43
|
-
# @raise [TermUtils::AP::ParseError]
|
44
|
-
# @raise [TermUtils::AP::SyntaxError]
|
45
|
-
def self.parse_command(syntax, command, opts = {})
|
46
|
-
TermUtils::AP::Parser.new.parse_command(syntax, command, opts)
|
47
|
-
end
|
101
|
+
|
48
102
|
# Parses a given list of arguments.
|
49
|
-
# @param syntax [
|
103
|
+
# @param syntax [Syntax]
|
50
104
|
# @param arguments [Array<String>]
|
51
105
|
# @param opts [Hash]
|
52
|
-
# @
|
53
|
-
# @
|
54
|
-
# @raise [
|
55
|
-
|
56
|
-
|
106
|
+
# @option opts [Boolean] :strict Whether the Syntax must be considered as strict.
|
107
|
+
# @return [Result]
|
108
|
+
# @raise [ParseError]
|
109
|
+
# @raise [SyntaxError]
|
110
|
+
def self.parse_arguments(syntax, arguments, opts = {}, &block)
|
111
|
+
TermUtils::AP::Parser.new.parse_arguments(syntax, arguments, opts, &block)
|
57
112
|
end
|
58
113
|
end
|
59
114
|
end
|
data/lib/term_utils/ff/config.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
# Copyright (C)
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (C) 2023 Thomas Baron
|
4
4
|
#
|
5
5
|
# This file is part of term_utils.
|
6
6
|
#
|
@@ -15,28 +15,40 @@
|
|
15
15
|
#
|
16
16
|
# You should have received a copy of the GNU General Public License
|
17
17
|
# along with term_utils. If not, see <https://www.gnu.org/licenses/>.
|
18
|
+
|
18
19
|
module TermUtils
|
19
|
-
# The ff module provides a way to find files.
|
20
20
|
module FF
|
21
21
|
# Represents a query configuration.
|
22
22
|
class Config
|
23
23
|
# @return [Array<Regexp>]
|
24
24
|
attr_accessor :ignore_list
|
25
|
-
# @return [Integer]
|
25
|
+
# @return [Integer, nil]
|
26
26
|
attr_accessor :min_depth
|
27
|
-
# @return [Integer]
|
27
|
+
# @return [Integer, nil]
|
28
28
|
attr_accessor :max_depth
|
29
|
-
# @return [
|
30
|
-
attr_accessor :
|
29
|
+
# @return [Symbol, nil] Either `:forward`, `:reverse` or `nil` (default).
|
30
|
+
attr_accessor :sorting_mode
|
31
|
+
# @return [Proc, nil]
|
32
|
+
attr_accessor :sorting_compare
|
33
|
+
|
34
|
+
# Constructs a new Config.
|
31
35
|
def initialize
|
32
36
|
@ignore_list = []
|
33
37
|
@min_depth = nil
|
34
38
|
@max_depth = nil
|
35
|
-
@
|
39
|
+
@sorting_mode = nil
|
40
|
+
@sorting_compare = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns whether the search is ordered.
|
44
|
+
# @return [Boolean]
|
45
|
+
def sorted?
|
46
|
+
(@sorting_mode == :forward) || (@sorting_mode == :reverse)
|
36
47
|
end
|
48
|
+
|
37
49
|
def initialize_copy(other)
|
38
|
-
@ignore_list = other.ignore_list.dup
|
39
50
|
super
|
51
|
+
@ignore_list = @ignore_list.dup
|
40
52
|
end
|
41
53
|
end
|
42
54
|
end
|
@@ -1,6 +1,6 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
# Copyright (C)
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (C) 2023 Thomas Baron
|
4
4
|
#
|
5
5
|
# This file is part of term_utils.
|
6
6
|
#
|
@@ -15,12 +15,30 @@
|
|
15
15
|
#
|
16
16
|
# You should have received a copy of the GNU General Public License
|
17
17
|
# along with term_utils. If not, see <https://www.gnu.org/licenses/>.
|
18
|
+
|
18
19
|
module TermUtils
|
19
|
-
module
|
20
|
-
#
|
21
|
-
class
|
22
|
-
|
23
|
-
|
20
|
+
module FF
|
21
|
+
# Represents an Entry of a Query result.
|
22
|
+
class Entry
|
23
|
+
# @return [Integer]
|
24
|
+
attr_accessor :index
|
25
|
+
# @return [String]
|
26
|
+
attr_accessor :name
|
27
|
+
# @return [Array<String>]
|
28
|
+
attr_accessor :relative_path_comps
|
29
|
+
# @return [String]
|
30
|
+
attr_accessor :path
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
@index = nil
|
34
|
+
@name = nil
|
35
|
+
@relative_path_comps = nil
|
36
|
+
@path = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [Integer]
|
40
|
+
def depth
|
41
|
+
@relative_path_comps.length
|
24
42
|
end
|
25
43
|
end
|
26
44
|
end
|
@@ -0,0 +1,255 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (C) 2023 Thomas Baron
|
4
|
+
#
|
5
|
+
# This file is part of term_utils.
|
6
|
+
#
|
7
|
+
# term_utils is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
9
|
+
# the Free Software Foundation, version 3 of the License.
|
10
|
+
#
|
11
|
+
# term_utils is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with term_utils. If not, see <https://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
module TermUtils
|
20
|
+
module FF
|
21
|
+
# Represents filesystem Entry.
|
22
|
+
class FinderEntry
|
23
|
+
# @return [Integer]
|
24
|
+
attr_accessor :index
|
25
|
+
# @return [String]
|
26
|
+
attr_accessor :name
|
27
|
+
# Identifies the type of stat. The return string is one of: “file”,
|
28
|
+
# “directory”, “characterSpecial”, “blockSpecial”, “fifo”, “link”,
|
29
|
+
# “socket”, or “unknown”.
|
30
|
+
# @return [String]
|
31
|
+
attr_accessor :kind
|
32
|
+
# Returns the numeric user id of the owner of stat.
|
33
|
+
# @return [Integer]
|
34
|
+
attr_accessor :uid
|
35
|
+
# Returns the numeric group id of the owner of stat.
|
36
|
+
# @return [Integer]
|
37
|
+
attr_accessor :gid
|
38
|
+
# Returns an integer representing the permission bits of stat. The meaning
|
39
|
+
# of the bits is platform dependent.
|
40
|
+
# @return [Integer]
|
41
|
+
attr_accessor :mode
|
42
|
+
# Returns the size of stat in bytes.
|
43
|
+
# @return [Integer]
|
44
|
+
attr_accessor :size
|
45
|
+
# @return [Array<String>]
|
46
|
+
attr_accessor :path_parts
|
47
|
+
# @return [String]
|
48
|
+
attr_accessor :path
|
49
|
+
|
50
|
+
def initialize
|
51
|
+
@index = nil
|
52
|
+
@name = nil
|
53
|
+
@kind = nil
|
54
|
+
@path_parts = nil
|
55
|
+
@path = nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def directory?
|
59
|
+
@kind == 'directory'
|
60
|
+
end
|
61
|
+
|
62
|
+
def file?
|
63
|
+
@kind == 'file'
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [Integer]
|
67
|
+
def depth
|
68
|
+
@path_parts.length
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Represents a Query.
|
73
|
+
class FinderQuery
|
74
|
+
# @return [Integer]
|
75
|
+
attr_accessor :min_depth
|
76
|
+
# @return [Integer, nil]
|
77
|
+
attr_accessor :max_depth
|
78
|
+
# @return [Boolean]
|
79
|
+
attr_accessor :use_stat
|
80
|
+
# @return [String, Array<String>, nil]
|
81
|
+
attr_accessor :entry_kind
|
82
|
+
|
83
|
+
def initialize
|
84
|
+
@min_depth = 0
|
85
|
+
@max_depth = nil
|
86
|
+
@use_stat = false
|
87
|
+
@entry_kind = nil
|
88
|
+
@filters = []
|
89
|
+
end
|
90
|
+
|
91
|
+
def filter(kind, &block)
|
92
|
+
raise StandardError, 'wrong filter kind' unless %i[enter skip include exclude].include?(kind)
|
93
|
+
raise StandardError, "missing #{kind} block" if block.nil?
|
94
|
+
|
95
|
+
@filters << { kind: kind, block: block }
|
96
|
+
end
|
97
|
+
|
98
|
+
# Wether to enter a directory.
|
99
|
+
def enter_directory(&block)
|
100
|
+
filter(:enter, &block)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Wether not to enter a directory.
|
104
|
+
def skip_directory(&block)
|
105
|
+
filter(:skip, &block)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Wether to include an entry into the results.
|
109
|
+
def include_entry(&block)
|
110
|
+
filter(:include, &block)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Wether to exclue an entry from the results.
|
114
|
+
def exclude_entry(&block)
|
115
|
+
filter(:exclude, &block)
|
116
|
+
end
|
117
|
+
|
118
|
+
def each_filter(&block)
|
119
|
+
@filters.each(&block)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Represents the find method engine.
|
124
|
+
class Finder
|
125
|
+
attr_reader :query
|
126
|
+
|
127
|
+
def initialize(paths)
|
128
|
+
@paths = paths.dup
|
129
|
+
@query = FinderQuery.new
|
130
|
+
end
|
131
|
+
|
132
|
+
def exec
|
133
|
+
@paths.each_with_object([]) do |path, obj|
|
134
|
+
ctx = { entries: obj, basedir: path }
|
135
|
+
list_start(ctx)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def list_start(ctx)
|
140
|
+
entry = FinderEntry.new.tap do |e|
|
141
|
+
e.index = ctx[:entries].length
|
142
|
+
e.name = File.basename(ctx[:basedir])
|
143
|
+
if @query.use_stat
|
144
|
+
st = File.stat(ctx[:basedir])
|
145
|
+
e.kind = st.ftype
|
146
|
+
e.uid = st.uid
|
147
|
+
e.gid = st.gid
|
148
|
+
e.mode = st.mode
|
149
|
+
e.size = st.size
|
150
|
+
end
|
151
|
+
e.path_parts = []
|
152
|
+
e.path = ctx[:basedir]
|
153
|
+
end
|
154
|
+
ctx[:entries] << entry if match?(entry) && include?(entry)
|
155
|
+
return unless enter?(entry)
|
156
|
+
|
157
|
+
list(ctx)
|
158
|
+
end
|
159
|
+
|
160
|
+
def list(ctx)
|
161
|
+
path_parts = ctx.fetch(:path_parts, [])
|
162
|
+
absolute_path =
|
163
|
+
if path_parts.empty?
|
164
|
+
ctx[:basedir]
|
165
|
+
else
|
166
|
+
"#{ctx[:basedir]}/#{path_parts.join('/')}"
|
167
|
+
end
|
168
|
+
entries = Dir.entries(absolute_path)
|
169
|
+
entries.each do |name|
|
170
|
+
next if %w[. ..].include?(name)
|
171
|
+
|
172
|
+
entry = FinderEntry.new.tap do |e|
|
173
|
+
e.index = ctx[:entries].length
|
174
|
+
e.name = name
|
175
|
+
if @query.use_stat
|
176
|
+
st = File.stat("#{absolute_path}/#{name}")
|
177
|
+
e.kind = st.ftype
|
178
|
+
e.uid = st.uid
|
179
|
+
e.gid = st.gid
|
180
|
+
e.mode = st.mode
|
181
|
+
e.size = st.size
|
182
|
+
end
|
183
|
+
e.path_parts = path_parts.dup + [name]
|
184
|
+
e.path = "#{ctx[:basedir]}/#{e.path_parts.join('/')}"
|
185
|
+
end
|
186
|
+
ctx[:entries] << entry if match?(entry) && include?(entry)
|
187
|
+
if enter?(entry)
|
188
|
+
list(ctx.merge({ path_parts: entry.path_parts }))
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def match?(entry)
|
194
|
+
# (1of2) Depth
|
195
|
+
return false if entry.depth < @query.min_depth
|
196
|
+
return false if !@query.max_depth.nil? && entry.depth > @query.max_depth
|
197
|
+
|
198
|
+
# (2of2) Entry kind.
|
199
|
+
if @query.entry_kind.nil?
|
200
|
+
true
|
201
|
+
elsif @query.entry_kind.is_a?(String)
|
202
|
+
entry.kind == @query.entry_kind
|
203
|
+
elsif @query.entry_kind.is_a?(Array)
|
204
|
+
@query.entry_kind.include?(entry.kind)
|
205
|
+
else
|
206
|
+
false
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def enter?(entry)
|
211
|
+
unless @query.use_stat
|
212
|
+
return File.directory?(entry.path)
|
213
|
+
end
|
214
|
+
|
215
|
+
return false unless entry.directory?
|
216
|
+
|
217
|
+
@query.each_filter do |f|
|
218
|
+
case f[:kind]
|
219
|
+
when :enter
|
220
|
+
return true if f[:block].call(entry)
|
221
|
+
when :skip
|
222
|
+
return false if f[:block].call(entry)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
true
|
226
|
+
end
|
227
|
+
|
228
|
+
def include?(entry)
|
229
|
+
@query.each_filter do |f|
|
230
|
+
case f[:kind]
|
231
|
+
when :include
|
232
|
+
return true if f[:block].call(entry)
|
233
|
+
when :exclude
|
234
|
+
return false if f[:block].call(entry)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
true
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# Finds files.
|
242
|
+
# @param paths [Array<String>]
|
243
|
+
# @return [Array<Hash>]
|
244
|
+
def self.find(*paths, &block)
|
245
|
+
fdr =
|
246
|
+
if paths.empty?
|
247
|
+
TermUtils::FF::Finder.new(['.'])
|
248
|
+
else
|
249
|
+
TermUtils::FF::Finder.new(paths)
|
250
|
+
end
|
251
|
+
block&.call(fdr.query)
|
252
|
+
fdr.exec
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
data/lib/term_utils/ff/query.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
# Copyright (C)
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (C) 2023 Thomas Baron
|
4
4
|
#
|
5
5
|
# This file is part of term_utils.
|
6
6
|
#
|
@@ -15,51 +15,128 @@
|
|
15
15
|
#
|
16
16
|
# You should have received a copy of the GNU General Public License
|
17
17
|
# along with term_utils. If not, see <https://www.gnu.org/licenses/>.
|
18
|
+
|
18
19
|
module TermUtils
|
19
|
-
# The ff module provides a way to find files.
|
20
20
|
module FF
|
21
|
+
# Query search context.
|
22
|
+
Context = Struct.new('Context', :config, :base_path, :block, :index_seq, :result)
|
21
23
|
# Represents a file system query.
|
22
24
|
class Query
|
25
|
+
# Constructs a new Query.
|
23
26
|
def initialize
|
24
27
|
@config = TermUtils::FF::Config.new
|
25
28
|
end
|
29
|
+
|
26
30
|
def initialize_copy(other)
|
27
|
-
@config = other.config.dup
|
28
31
|
super
|
32
|
+
@config = @config.dup
|
29
33
|
end
|
30
|
-
|
34
|
+
|
35
|
+
# Adds a Regexp for ignoring file names.
|
31
36
|
# @param regexp [Regexp]
|
32
|
-
# @return [
|
37
|
+
# @return [Query]
|
33
38
|
def ignore(regexp)
|
34
39
|
@config.ignore_list << regexp
|
35
40
|
self
|
36
41
|
end
|
42
|
+
|
37
43
|
# Sets a minimum depth.
|
38
44
|
# @param depth [Integer]
|
39
|
-
# @return [
|
45
|
+
# @return [Query]
|
40
46
|
def min_depth(depth)
|
41
47
|
@config.min_depth = depth
|
42
48
|
self
|
43
49
|
end
|
50
|
+
|
44
51
|
# Sets a maximum depth.
|
45
52
|
# @param depth [Integer]
|
46
|
-
# @return [
|
53
|
+
# @return [Query]
|
47
54
|
def max_depth(depth)
|
48
55
|
@config.max_depth = depth
|
49
56
|
self
|
50
57
|
end
|
51
|
-
|
52
|
-
#
|
53
|
-
# @
|
54
|
-
|
55
|
-
|
58
|
+
|
59
|
+
# Sets the sorting mode.
|
60
|
+
# @param mode [Symbol] Either `:forward`, `:reverse` or `nil` (default).
|
61
|
+
# @return [Query]
|
62
|
+
def sort(mode = :forward, &block)
|
63
|
+
@config.sorting_mode = mode
|
64
|
+
@config.sorting_compare = block
|
56
65
|
self
|
57
66
|
end
|
67
|
+
|
58
68
|
# Executes this one.
|
59
69
|
# @param path [String]
|
60
|
-
# @return [
|
61
|
-
def exec(path)
|
62
|
-
|
70
|
+
# @return [Array<Entry>]
|
71
|
+
def exec(path, &block)
|
72
|
+
ctx = Context.new
|
73
|
+
ctx.config = @config.dup
|
74
|
+
ctx.config.min_depth = 0 if ctx.config.min_depth.nil?
|
75
|
+
ctx.base_path = path
|
76
|
+
ctx.block = block
|
77
|
+
ctx.index_seq = 0
|
78
|
+
ctx.result = []
|
79
|
+
first_entry = TermUtils::FF::Entry.new
|
80
|
+
first_entry.name = path
|
81
|
+
first_entry.relative_path_comps = []
|
82
|
+
first_entry.path = path
|
83
|
+
if ctx.config.min_depth == 0
|
84
|
+
first_entry.index = ctx.index_seq
|
85
|
+
ctx.index_seq += 1
|
86
|
+
ctx.block.call(first_entry) if ctx.block
|
87
|
+
ctx.result << first_entry
|
88
|
+
end
|
89
|
+
if File.directory?(first_entry.path) && (ctx.config.max_depth.nil? || ctx.config.max_depth > 0)
|
90
|
+
TermUtils::FF::Query.search(ctx, first_entry)
|
91
|
+
end
|
92
|
+
ctx.result
|
93
|
+
end
|
94
|
+
|
95
|
+
# Searches a directory.
|
96
|
+
# @param ctx [Context]
|
97
|
+
# @param parent_entry [entry]
|
98
|
+
# @return [nil]
|
99
|
+
def self.search(ctx, parent_entry)
|
100
|
+
entries = Dir.entries(parent_entry.path)
|
101
|
+
unless ctx.config.sorting_mode.nil?
|
102
|
+
if ctx.config.sorting_compare.nil?
|
103
|
+
entries.sort!
|
104
|
+
else
|
105
|
+
entries.sort!(&ctx.config.sorting_compare)
|
106
|
+
end
|
107
|
+
if ctx.config.sorting_mode == :reverse
|
108
|
+
entries.reverse!
|
109
|
+
end
|
110
|
+
end
|
111
|
+
entries.each do |name|
|
112
|
+
next if %w[. ..].include? name
|
113
|
+
next unless accept_entry_name?(ctx, name)
|
114
|
+
|
115
|
+
new_entry = TermUtils::FF::Entry.new
|
116
|
+
new_entry.name = name
|
117
|
+
new_entry.relative_path_comps = parent_entry.relative_path_comps.dup.push(name)
|
118
|
+
new_entry.path = "#{ctx.base_path}/#{new_entry.relative_path_comps.join('/')}"
|
119
|
+
if ctx.config.min_depth <= new_entry.depth
|
120
|
+
new_entry.index = ctx.index_seq
|
121
|
+
ctx.index_seq += 1
|
122
|
+
ctx.block.call(new_entry) if ctx.block
|
123
|
+
ctx.result << new_entry
|
124
|
+
end
|
125
|
+
if File.directory?(new_entry.path) && (ctx.config.max_depth.nil? || ctx.config.max_depth > new_entry.depth)
|
126
|
+
TermUtils::FF::Query.search(ctx, new_entry)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Tests whether a given entry should be accepted.
|
132
|
+
# @param ctx [Context]
|
133
|
+
# @param name [String]
|
134
|
+
# @return [Boolean]
|
135
|
+
def self.accept_entry_name?(ctx, name)
|
136
|
+
ctx.config.ignore_list.each do |e|
|
137
|
+
return false if e.match?(name)
|
138
|
+
end
|
139
|
+
true
|
63
140
|
end
|
64
141
|
end
|
65
142
|
end
|
data/lib/term_utils/ff.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (C) 2023 Thomas Baron
|
2
4
|
#
|
3
5
|
# This file is part of term_utils.
|
4
6
|
#
|
@@ -13,6 +15,14 @@
|
|
13
15
|
#
|
14
16
|
# You should have received a copy of the GNU General Public License
|
15
17
|
# along with term_utils. If not, see <https://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
module TermUtils
|
20
|
+
# Provides ways to find files.
|
21
|
+
module FF
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
16
25
|
require 'term_utils/ff/config'
|
17
|
-
require 'term_utils/ff/
|
26
|
+
require 'term_utils/ff/entry'
|
18
27
|
require 'term_utils/ff/query'
|
28
|
+
require 'term_utils/ff/finder'
|