toys-core 0.11.5 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +62 -0
- data/LICENSE.md +1 -1
- data/README.md +5 -2
- data/docs/guide.md +1 -1
- data/lib/toys/acceptor.rb +13 -4
- data/lib/toys/arg_parser.rb +7 -7
- data/lib/toys/cli.rb +170 -120
- data/lib/toys/compat.rb +71 -23
- data/lib/toys/completion.rb +18 -6
- data/lib/toys/context.rb +24 -15
- data/lib/toys/core.rb +6 -2
- data/lib/toys/dsl/base.rb +87 -0
- data/lib/toys/dsl/flag.rb +26 -20
- data/lib/toys/dsl/flag_group.rb +18 -14
- data/lib/toys/dsl/internal.rb +206 -0
- data/lib/toys/dsl/positional_arg.rb +26 -16
- data/lib/toys/dsl/tool.rb +180 -218
- data/lib/toys/errors.rb +64 -8
- data/lib/toys/flag.rb +662 -656
- data/lib/toys/flag_group.rb +24 -10
- data/lib/toys/input_file.rb +13 -7
- data/lib/toys/loader.rb +293 -140
- data/lib/toys/middleware.rb +46 -22
- data/lib/toys/mixin.rb +10 -8
- data/lib/toys/positional_arg.rb +21 -20
- data/lib/toys/settings.rb +914 -0
- data/lib/toys/source_info.rb +147 -35
- data/lib/toys/standard_middleware/add_verbosity_flags.rb +2 -0
- data/lib/toys/standard_middleware/apply_config.rb +6 -4
- data/lib/toys/standard_middleware/handle_usage_errors.rb +1 -0
- data/lib/toys/standard_middleware/set_default_descriptions.rb +19 -18
- data/lib/toys/standard_middleware/show_help.rb +19 -5
- data/lib/toys/standard_middleware/show_root_version.rb +2 -0
- data/lib/toys/standard_mixins/bundler.rb +24 -15
- data/lib/toys/standard_mixins/exec.rb +43 -34
- data/lib/toys/standard_mixins/fileutils.rb +3 -1
- data/lib/toys/standard_mixins/gems.rb +21 -17
- data/lib/toys/standard_mixins/git_cache.rb +46 -0
- data/lib/toys/standard_mixins/highline.rb +8 -8
- data/lib/toys/standard_mixins/terminal.rb +5 -5
- data/lib/toys/standard_mixins/xdg.rb +56 -0
- data/lib/toys/template.rb +11 -9
- data/lib/toys/{tool.rb → tool_definition.rb} +292 -226
- data/lib/toys/utils/completion_engine.rb +7 -2
- data/lib/toys/utils/exec.rb +162 -132
- data/lib/toys/utils/gems.rb +85 -60
- data/lib/toys/utils/git_cache.rb +813 -0
- data/lib/toys/utils/help_text.rb +117 -37
- data/lib/toys/utils/terminal.rb +11 -3
- data/lib/toys/utils/xdg.rb +293 -0
- data/lib/toys/wrappable_string.rb +9 -2
- data/lib/toys-core.rb +18 -6
- metadata +14 -7
data/lib/toys/utils/help_text.rb
CHANGED
@@ -44,11 +44,12 @@ module Toys
|
|
44
44
|
##
|
45
45
|
# Create a usage helper.
|
46
46
|
#
|
47
|
-
# @param tool [Toys::
|
47
|
+
# @param tool [Toys::ToolDefinition] The tool to document.
|
48
48
|
# @param loader [Toys::Loader] A loader that can provide subcommands.
|
49
49
|
# @param executable_name [String] The name of the executable.
|
50
50
|
# e.g. `"toys"`.
|
51
|
-
# @param delegates [Array<Toys::
|
51
|
+
# @param delegates [Array<Toys::ToolDefinition>] The delegation path to
|
52
|
+
# the tool.
|
52
53
|
#
|
53
54
|
# @return [Toys::Utils::HelpText]
|
54
55
|
#
|
@@ -60,8 +61,8 @@ module Toys
|
|
60
61
|
end
|
61
62
|
|
62
63
|
##
|
63
|
-
# The
|
64
|
-
# @return [Toys::
|
64
|
+
# The ToolDefinition being documented.
|
65
|
+
# @return [Toys::ToolDefinition]
|
65
66
|
#
|
66
67
|
attr_reader :tool
|
67
68
|
|
@@ -72,6 +73,8 @@ module Toys
|
|
72
73
|
# display all subtools recursively. Defaults to false.
|
73
74
|
# @param include_hidden [Boolean] Include hidden subtools (i.e. whose
|
74
75
|
# names begin with underscore.) Default is false.
|
76
|
+
# @param separate_sources [Boolean] Split up tool list by source root.
|
77
|
+
# Defaults to false.
|
75
78
|
# @param left_column_width [Integer] Width of the first column. Default
|
76
79
|
# is {DEFAULT_LEFT_COLUMN_WIDTH}.
|
77
80
|
# @param indent [Integer] Indent width. Default is {DEFAULT_INDENT}.
|
@@ -80,13 +83,14 @@ module Toys
|
|
80
83
|
#
|
81
84
|
# @return [String] A usage string.
|
82
85
|
#
|
83
|
-
def usage_string(recursive: false, include_hidden: false,
|
86
|
+
def usage_string(recursive: false, include_hidden: false, separate_sources: false,
|
84
87
|
left_column_width: nil, indent: nil, wrap_width: nil)
|
85
88
|
left_column_width ||= DEFAULT_LEFT_COLUMN_WIDTH
|
86
89
|
indent ||= DEFAULT_INDENT
|
87
|
-
subtools =
|
90
|
+
subtools = collect_subtool_info(recursive, nil, include_hidden, separate_sources)
|
88
91
|
assembler = UsageStringAssembler.new(
|
89
|
-
@tool, @executable_name, subtools,
|
92
|
+
@tool, @executable_name, subtools, separate_sources,
|
93
|
+
indent, left_column_width, wrap_width
|
90
94
|
)
|
91
95
|
assembler.result
|
92
96
|
end
|
@@ -102,6 +106,8 @@ module Toys
|
|
102
106
|
# names begin with underscore.) Default is false.
|
103
107
|
# @param show_source_path [Boolean] If true, shows the source path
|
104
108
|
# section. Defaults to false.
|
109
|
+
# @param separate_sources [Boolean] Split up tool list by source root.
|
110
|
+
# Defaults to false.
|
105
111
|
# @param indent [Integer] Indent width. Default is {DEFAULT_INDENT}.
|
106
112
|
# @param indent2 [Integer] Second indent width. Default is
|
107
113
|
# {DEFAULT_INDENT}.
|
@@ -112,13 +118,14 @@ module Toys
|
|
112
118
|
# @return [String] A usage string.
|
113
119
|
#
|
114
120
|
def help_string(recursive: false, search: nil, include_hidden: false,
|
115
|
-
show_source_path: false,
|
121
|
+
show_source_path: false, separate_sources: false,
|
116
122
|
indent: nil, indent2: nil, wrap_width: nil, styled: true)
|
117
123
|
indent ||= DEFAULT_INDENT
|
118
124
|
indent2 ||= DEFAULT_INDENT
|
119
|
-
subtools =
|
125
|
+
subtools = collect_subtool_info(recursive, search, include_hidden, separate_sources)
|
120
126
|
assembler = HelpStringAssembler.new(
|
121
|
-
@tool, @executable_name, @delegates, subtools, search,
|
127
|
+
@tool, @executable_name, @delegates, subtools, search,
|
128
|
+
show_source_path, separate_sources,
|
122
129
|
indent, indent2, wrap_width, styled
|
123
130
|
)
|
124
131
|
assembler.result
|
@@ -133,6 +140,8 @@ module Toys
|
|
133
140
|
# listing subtools. Defaults to `nil` which finds all subtools.
|
134
141
|
# @param include_hidden [Boolean] Include hidden subtools (i.e. whose
|
135
142
|
# names begin with underscore.) Default is false.
|
143
|
+
# @param separate_sources [Boolean] Split up tool list by source root.
|
144
|
+
# Defaults to false.
|
136
145
|
# @param indent [Integer] Indent width. Default is {DEFAULT_INDENT}.
|
137
146
|
# @param wrap_width [Integer,nil] Wrap width of the column, or `nil` to
|
138
147
|
# disable wrap. Default is `nil`.
|
@@ -141,17 +150,23 @@ module Toys
|
|
141
150
|
# @return [String] A usage string.
|
142
151
|
#
|
143
152
|
def list_string(recursive: false, search: nil, include_hidden: false,
|
144
|
-
indent: nil, wrap_width: nil, styled: true)
|
153
|
+
separate_sources: false, indent: nil, wrap_width: nil, styled: true)
|
145
154
|
indent ||= DEFAULT_INDENT
|
146
|
-
subtools =
|
147
|
-
assembler = ListStringAssembler.new(@tool, subtools, recursive, search,
|
155
|
+
subtools = collect_subtool_info(recursive, search, include_hidden, separate_sources)
|
156
|
+
assembler = ListStringAssembler.new(@tool, subtools, recursive, search, separate_sources,
|
148
157
|
indent, wrap_width, styled)
|
149
158
|
assembler.result
|
150
159
|
end
|
151
160
|
|
152
161
|
private
|
153
162
|
|
154
|
-
def
|
163
|
+
def collect_subtool_info(recursive, search, include_hidden, separate_sources)
|
164
|
+
subtools_by_name = list_subtools(recursive, include_hidden)
|
165
|
+
filter_subtools(subtools_by_name, search)
|
166
|
+
arrange_subtools(subtools_by_name, separate_sources)
|
167
|
+
end
|
168
|
+
|
169
|
+
def list_subtools(recursive, include_hidden)
|
155
170
|
subtools_by_name = {}
|
156
171
|
([@tool] + @delegates).each do |tool|
|
157
172
|
name_len = tool.full_name.length
|
@@ -162,20 +177,42 @@ module Toys
|
|
162
177
|
subtools_by_name[local_name] = subtool
|
163
178
|
end
|
164
179
|
end
|
180
|
+
subtools_by_name
|
181
|
+
end
|
182
|
+
|
183
|
+
def filter_subtools(subtools_by_name, search)
|
184
|
+
if !search.nil? && !search.empty?
|
185
|
+
regex = ::Regexp.new(search, ::Regexp::IGNORECASE)
|
186
|
+
subtools_by_name.delete_if do |local_name, tool|
|
187
|
+
!regex.match?(local_name) && !regex.match?(tool.desc.to_s)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def arrange_subtools(subtools_by_name, separate_sources)
|
165
193
|
subtool_list = subtools_by_name.sort_by { |(local_name, _tool)| local_name }
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
194
|
+
result = {}
|
195
|
+
subtool_list.each do |(local_name, subtool)|
|
196
|
+
key = separate_sources ? subtool.source_root : nil
|
197
|
+
(result[key] ||= []) << [local_name, subtool]
|
170
198
|
end
|
199
|
+
result.sort_by { |source, _subtools| -(source&.priority || -999_999) }
|
200
|
+
.map { |source, subtools| [source&.source_name || "unknown source", subtools] }
|
171
201
|
end
|
172
202
|
|
173
|
-
##
|
203
|
+
##
|
204
|
+
# @private
|
205
|
+
#
|
174
206
|
class UsageStringAssembler
|
175
|
-
|
207
|
+
##
|
208
|
+
# @private
|
209
|
+
#
|
210
|
+
def initialize(tool, executable_name, subtools, separate_sources,
|
211
|
+
indent, left_column_width, wrap_width)
|
176
212
|
@tool = tool
|
177
213
|
@executable_name = executable_name
|
178
214
|
@subtools = subtools
|
215
|
+
@separate_sources = separate_sources
|
179
216
|
@indent = indent
|
180
217
|
@left_column_width = left_column_width
|
181
218
|
@wrap_width = wrap_width
|
@@ -184,6 +221,9 @@ module Toys
|
|
184
221
|
assemble
|
185
222
|
end
|
186
223
|
|
224
|
+
##
|
225
|
+
# @private
|
226
|
+
#
|
187
227
|
attr_reader :result
|
188
228
|
|
189
229
|
private
|
@@ -193,7 +233,8 @@ module Toys
|
|
193
233
|
add_flag_group_sections
|
194
234
|
add_positional_arguments_section if @tool.runnable?
|
195
235
|
add_subtool_list_section
|
196
|
-
|
236
|
+
joined_lines = @lines.join("\n")
|
237
|
+
@result = "#{joined_lines}\n"
|
197
238
|
end
|
198
239
|
|
199
240
|
def add_synopsis_section
|
@@ -226,7 +267,7 @@ module Toys
|
|
226
267
|
@lines << ""
|
227
268
|
desc_str = group.desc.to_s
|
228
269
|
desc_str = "Flags" if desc_str.empty?
|
229
|
-
@lines << desc_str
|
270
|
+
@lines << "#{desc_str}:"
|
230
271
|
group.flags.each do |flag|
|
231
272
|
add_flag(flag)
|
232
273
|
end
|
@@ -254,11 +295,12 @@ module Toys
|
|
254
295
|
end
|
255
296
|
|
256
297
|
def add_subtool_list_section
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
298
|
+
@subtools.each do |source_name, subtool_list|
|
299
|
+
@lines << ""
|
300
|
+
@lines << (@separate_sources ? "Tools from #{source_name}:" : "Tools:")
|
301
|
+
subtool_list.each do |local_name, subtool|
|
302
|
+
add_right_column_desc(local_name, wrap_desc(subtool.desc))
|
303
|
+
end
|
262
304
|
end
|
263
305
|
end
|
264
306
|
|
@@ -296,10 +338,15 @@ module Toys
|
|
296
338
|
end
|
297
339
|
end
|
298
340
|
|
299
|
-
##
|
341
|
+
##
|
342
|
+
# @private
|
343
|
+
#
|
300
344
|
class HelpStringAssembler
|
345
|
+
##
|
346
|
+
# @private
|
347
|
+
#
|
301
348
|
def initialize(tool, executable_name, delegates, subtools, search_term,
|
302
|
-
show_source_path, indent, indent2, wrap_width, styled)
|
349
|
+
show_source_path, separate_sources, indent, indent2, wrap_width, styled)
|
303
350
|
require "toys/utils/terminal"
|
304
351
|
@tool = tool
|
305
352
|
@executable_name = executable_name
|
@@ -307,6 +354,7 @@ module Toys
|
|
307
354
|
@subtools = subtools
|
308
355
|
@search_term = search_term
|
309
356
|
@show_source_path = show_source_path
|
357
|
+
@separate_sources = separate_sources
|
310
358
|
@indent = indent
|
311
359
|
@indent2 = indent2
|
312
360
|
@wrap_width = wrap_width
|
@@ -314,6 +362,9 @@ module Toys
|
|
314
362
|
assemble
|
315
363
|
end
|
316
364
|
|
365
|
+
##
|
366
|
+
# @private
|
367
|
+
#
|
317
368
|
attr_reader :result
|
318
369
|
|
319
370
|
private
|
@@ -532,8 +583,17 @@ module Toys
|
|
532
583
|
@lines << indent_str("Showing search results for \"#{@search_term}\"")
|
533
584
|
@lines << ""
|
534
585
|
end
|
535
|
-
|
536
|
-
|
586
|
+
first_section = true
|
587
|
+
@subtools.each do |source_name, subtool_list|
|
588
|
+
@lines << "" unless first_section
|
589
|
+
if @separate_sources
|
590
|
+
@lines << indent_str(underline("From #{source_name}"))
|
591
|
+
@lines << ""
|
592
|
+
end
|
593
|
+
subtool_list.each do |local_name, subtool|
|
594
|
+
add_prefix_with_desc(bold(local_name), subtool.desc)
|
595
|
+
end
|
596
|
+
first_section = false
|
537
597
|
end
|
538
598
|
end
|
539
599
|
|
@@ -594,19 +654,29 @@ module Toys
|
|
594
654
|
end
|
595
655
|
end
|
596
656
|
|
597
|
-
##
|
657
|
+
##
|
658
|
+
# @private
|
659
|
+
#
|
598
660
|
class ListStringAssembler
|
599
|
-
|
661
|
+
##
|
662
|
+
# @private
|
663
|
+
#
|
664
|
+
def initialize(tool, subtools, recursive, search_term, separate_sources,
|
665
|
+
indent, wrap_width, styled)
|
600
666
|
require "toys/utils/terminal"
|
601
667
|
@tool = tool
|
602
668
|
@subtools = subtools
|
603
669
|
@recursive = recursive
|
604
670
|
@search_term = search_term
|
671
|
+
@separate_sources = separate_sources
|
605
672
|
@indent = indent
|
606
673
|
@wrap_width = wrap_width
|
607
674
|
assemble(styled)
|
608
675
|
end
|
609
676
|
|
677
|
+
##
|
678
|
+
# @private
|
679
|
+
#
|
610
680
|
attr_reader :result
|
611
681
|
|
612
682
|
private
|
@@ -626,16 +696,22 @@ module Toys
|
|
626
696
|
else
|
627
697
|
"#{top_line} under #{bold(@tool.display_name)}:"
|
628
698
|
end
|
629
|
-
@lines << ""
|
630
699
|
if @search_term
|
631
|
-
@lines << "Showing search results for \"#{@search_term}\""
|
632
700
|
@lines << ""
|
701
|
+
@lines << "Showing search results for \"#{@search_term}\""
|
633
702
|
end
|
634
703
|
end
|
635
704
|
|
636
705
|
def add_list
|
637
|
-
@subtools.each do |
|
638
|
-
|
706
|
+
@subtools.each do |source_name, subtool_list|
|
707
|
+
@lines << ""
|
708
|
+
if @separate_sources
|
709
|
+
@lines << underline("From: #{source_name}")
|
710
|
+
@lines << ""
|
711
|
+
end
|
712
|
+
subtool_list.each do |local_name, subtool|
|
713
|
+
add_prefix_with_desc(bold(local_name), subtool.desc)
|
714
|
+
end
|
639
715
|
end
|
640
716
|
end
|
641
717
|
|
@@ -662,6 +738,10 @@ module Toys
|
|
662
738
|
@lines.apply_styles(str, :bold)
|
663
739
|
end
|
664
740
|
|
741
|
+
def underline(str)
|
742
|
+
@lines.apply_styles(str, :underline)
|
743
|
+
end
|
744
|
+
|
665
745
|
def indent_str(str)
|
666
746
|
"#{' ' * @indent}#{str}"
|
667
747
|
end
|
data/lib/toys/utils/terminal.rb
CHANGED
@@ -14,7 +14,7 @@ module Toys
|
|
14
14
|
##
|
15
15
|
# A simple terminal class.
|
16
16
|
#
|
17
|
-
#
|
17
|
+
# ### Styles
|
18
18
|
#
|
19
19
|
# This class supports ANSI styled output where supported.
|
20
20
|
#
|
@@ -120,7 +120,7 @@ module Toys
|
|
120
120
|
@output = output
|
121
121
|
@styled =
|
122
122
|
if styled.nil?
|
123
|
-
output.respond_to?(:tty?) && output.tty?
|
123
|
+
output.respond_to?(:tty?) && output.tty? && !::ENV["NO_COLOR"]
|
124
124
|
else
|
125
125
|
styled ? true : false
|
126
126
|
end
|
@@ -431,8 +431,13 @@ module Toys
|
|
431
431
|
end
|
432
432
|
end
|
433
433
|
|
434
|
-
##
|
434
|
+
##
|
435
|
+
# @private
|
436
|
+
#
|
435
437
|
class SpinDriver
|
438
|
+
##
|
439
|
+
# @private
|
440
|
+
#
|
436
441
|
def initialize(terminal, frames, style, frame_length)
|
437
442
|
@mutex = ::Monitor.new
|
438
443
|
@terminal = terminal
|
@@ -446,6 +451,9 @@ module Toys
|
|
446
451
|
@thread = @terminal.output.tty? ? start_thread : nil
|
447
452
|
end
|
448
453
|
|
454
|
+
##
|
455
|
+
# @private
|
456
|
+
#
|
449
457
|
def stop
|
450
458
|
@mutex.synchronize do
|
451
459
|
@stopping = true
|
@@ -0,0 +1,293 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Toys
|
4
|
+
module Utils
|
5
|
+
##
|
6
|
+
# A class that provides tools for working with the XDG Base Directory
|
7
|
+
# Specification.
|
8
|
+
#
|
9
|
+
# This class provides utility methods that locate base directories and
|
10
|
+
# search paths for application state, configuration, caches, and other
|
11
|
+
# data, according to the [XDG Base Directory Spec version
|
12
|
+
# 0.8](https://specifications.freedesktop.org/basedir-spec/0.8/).
|
13
|
+
#
|
14
|
+
# Tools can use the `:xdg` mixin for convenient access to this class.
|
15
|
+
#
|
16
|
+
# ### Example
|
17
|
+
#
|
18
|
+
# require "toys/utils/xdg"
|
19
|
+
#
|
20
|
+
# xdg = Toys::Utils::XDG.new
|
21
|
+
#
|
22
|
+
# # Get config file paths, in order from most to least inportant
|
23
|
+
# config_files = xdg.lookup_config("my-config.toml")
|
24
|
+
# config_files.each { |path| read_my_config(path) }
|
25
|
+
#
|
26
|
+
# ### Windows operation
|
27
|
+
#
|
28
|
+
# The Spec assumes a unix-like environment, and cannot be applied directly
|
29
|
+
# to Windows without modification. In general, this class will function on
|
30
|
+
# Windows, but with the following caveats:
|
31
|
+
#
|
32
|
+
# * All file paths must use Windows-style absolute paths, beginning with
|
33
|
+
# the drive letter.
|
34
|
+
# * Environment variables that can contain multiple paths (`XDG_*_DIRS`)
|
35
|
+
# use the Windows path delimiter (`;`) rather than the unix path
|
36
|
+
# delimiter (`:`).
|
37
|
+
# * Defaults for home directories (`XDG_*_HOME`) will follow unix
|
38
|
+
# conventions, using subdirectories under the user's profile directory
|
39
|
+
# rather than the Windows known folder paths.
|
40
|
+
# * Defaults for search paths (`XDG_*_DIRS`) will be empty and will not
|
41
|
+
# use the Windows known folder paths.
|
42
|
+
#
|
43
|
+
class XDG
|
44
|
+
##
|
45
|
+
# Create an instance of XDG.
|
46
|
+
#
|
47
|
+
# @param env [Hash{String=>String}] the environment variables. Normally,
|
48
|
+
# you can omit this argument, as it will default to `::ENV`.
|
49
|
+
#
|
50
|
+
def initialize(env: ::ENV)
|
51
|
+
@env = env
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Returns the absolute path to the current user's home directory.
|
56
|
+
#
|
57
|
+
# @return [String]
|
58
|
+
#
|
59
|
+
def home_dir
|
60
|
+
@home_dir ||= validate_dir_env("HOME") || ::Dir.home
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# Returns the absolute path to the single base directory relative to
|
65
|
+
# which user-specific data files should be written.
|
66
|
+
# Corresponds to the value of the `$XDG_DATA_HOME` environment variable
|
67
|
+
# and its defaults according to the XDG Base Directory Spec.
|
68
|
+
#
|
69
|
+
# @return [String]
|
70
|
+
#
|
71
|
+
def data_home
|
72
|
+
@data_home ||=
|
73
|
+
validate_dir_env("XDG_DATA_HOME") || ::File.join(home_dir, ".local", "share")
|
74
|
+
end
|
75
|
+
|
76
|
+
##
|
77
|
+
# Returns the absolute path to the single base directory relative to
|
78
|
+
# which user-specific configuration files should be written.
|
79
|
+
# Corresponds to the value of the `$XDG_CONFIG_HOME` environment variable
|
80
|
+
# and its defaults according to the XDG Base Directory Spec.
|
81
|
+
#
|
82
|
+
# @return [String]
|
83
|
+
#
|
84
|
+
def config_home
|
85
|
+
@config_home ||= validate_dir_env("XDG_CONFIG_HOME") || ::File.join(home_dir, ".config")
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Returns the absolute path to the single base directory relative to
|
90
|
+
# which user-specific state files should be written.
|
91
|
+
# Corresponds to the value of the `$XDG_STATE_HOME` environment variable
|
92
|
+
# and its defaults according to the XDG Base Directory Spec.
|
93
|
+
#
|
94
|
+
# @return [String]
|
95
|
+
#
|
96
|
+
def state_home
|
97
|
+
@state_home ||=
|
98
|
+
validate_dir_env("XDG_STATE_HOME") || ::File.join(home_dir, ".local", "state")
|
99
|
+
end
|
100
|
+
|
101
|
+
##
|
102
|
+
# Returns the absolute path to the single base directory relative to
|
103
|
+
# which user-specific non-essential (cached) data should be written.
|
104
|
+
# Corresponds to the value of the `$XDG_CACHE_HOME` environment variable
|
105
|
+
# and its defaults according to the XDG Base Directory Spec.
|
106
|
+
#
|
107
|
+
# @return [String]
|
108
|
+
#
|
109
|
+
def cache_home
|
110
|
+
@cache_home ||= validate_dir_env("XDG_CACHE_HOME") || ::File.join(home_dir, ".cache")
|
111
|
+
end
|
112
|
+
|
113
|
+
##
|
114
|
+
# Returns the absolute path to the single base directory relative to
|
115
|
+
# which user-specific executable files may be written.
|
116
|
+
# Returns the value of `$HOME/.local/bin` as specified by the XDG Base
|
117
|
+
# Directory Spec.
|
118
|
+
#
|
119
|
+
# @return [String]
|
120
|
+
#
|
121
|
+
def executable_home
|
122
|
+
@executable_home ||= ::File.join(home_dir, ".local", "bin")
|
123
|
+
end
|
124
|
+
|
125
|
+
##
|
126
|
+
# Returns the set of preference ordered base directories relative to
|
127
|
+
# which data files should be searched, as an array of absolute paths.
|
128
|
+
# The array is ordered from most to least important, and does _not_
|
129
|
+
# include the data home directory.
|
130
|
+
# Corresponds to the value of the `$XDG_DATA_DIRS` environment variable
|
131
|
+
# and its defaults according to the XDG Base Directory Spec.
|
132
|
+
#
|
133
|
+
# @return [Array<String>]
|
134
|
+
#
|
135
|
+
def data_dirs
|
136
|
+
@data_dirs ||= validate_dirs_env("XDG_DATA_DIRS") ||
|
137
|
+
validate_dirs(["/usr/local/share", "/usr/share"]) || []
|
138
|
+
end
|
139
|
+
|
140
|
+
##
|
141
|
+
# Returns the set of preference ordered base directories relative to
|
142
|
+
# which configuration files should be searched, as an array of absolute
|
143
|
+
# paths. The array is ordered from most to least important, and does
|
144
|
+
# _not_ include the config home directory.
|
145
|
+
# Corresponds to the value of the `$XDG_CONFIG_DIRS` environment variable
|
146
|
+
# and its defaults according to the XDG Base Directory Spec.
|
147
|
+
#
|
148
|
+
# @return [Array<String>]
|
149
|
+
#
|
150
|
+
def config_dirs
|
151
|
+
@config_dirs ||= validate_dirs_env("XDG_CONFIG_DIRS") ||
|
152
|
+
validate_dirs(["/etc/xdg"]) || []
|
153
|
+
end
|
154
|
+
|
155
|
+
##
|
156
|
+
# Returns the absolute path to the single base directory relative to
|
157
|
+
# which user-specific runtime files and other file objects should be
|
158
|
+
# placed. May return `nil` if no such directory could be determined.
|
159
|
+
#
|
160
|
+
# @return [String,nil]
|
161
|
+
#
|
162
|
+
def runtime_dir
|
163
|
+
@runtime_dir = validate_dir_env("XDG_RUNTIME_DIR") unless defined? @runtime_dir
|
164
|
+
@runtime_dir
|
165
|
+
end
|
166
|
+
|
167
|
+
##
|
168
|
+
# Searches the data directories for an object with the given relative
|
169
|
+
# path, and returns an array of absolute paths to all objects found in
|
170
|
+
# the data directories (i.e. in {#data_dirs} or {#data_home}), in order
|
171
|
+
# from most to least important.
|
172
|
+
#
|
173
|
+
# @param path [String] Relative path of the object to search for
|
174
|
+
# @param type [String,Symbol,Array<String,Symbol>] The type(s) of objects
|
175
|
+
# to find. You can specify any of the types defined by
|
176
|
+
# [File::Stat#ftype](https://ruby-doc.org/core/File/Stat.html#method-i-ftype),
|
177
|
+
# such as `file` or `directory`, or the special type `any`. Types can
|
178
|
+
# be specified as strings or the corresponding symbols. If this
|
179
|
+
# argument is not provided, the default of `file` is used.
|
180
|
+
# @return [Array<String>]
|
181
|
+
#
|
182
|
+
def lookup_data(path, type: :file)
|
183
|
+
lookup_internal([data_home] + data_dirs, path, type)
|
184
|
+
end
|
185
|
+
|
186
|
+
##
|
187
|
+
# Searches the config directories for an object with the given relative
|
188
|
+
# path, and returns an array of absolute paths to all objects found in
|
189
|
+
# the config directories (i.e. in {#config_dirs} or {#config_home}), in
|
190
|
+
# order from most to least important.
|
191
|
+
#
|
192
|
+
# @param path [String] Relative path of the object to search for
|
193
|
+
# @param type [String,Symbol,Array<String,Symbol>] The type(s) of objects
|
194
|
+
# to find. You can specify any of the types defined by
|
195
|
+
# [File::Stat#ftype](https://ruby-doc.org/core/File/Stat.html#method-i-ftype),
|
196
|
+
# such as `file` or `directory`, or the special type `any`. Types can
|
197
|
+
# be specified as strings or the corresponding symbols. If this
|
198
|
+
# argument is not provided, the default of `file` is used.
|
199
|
+
# @return [Array<String>]
|
200
|
+
#
|
201
|
+
def lookup_config(path, type: :file)
|
202
|
+
lookup_internal([config_home] + config_dirs, path, type)
|
203
|
+
end
|
204
|
+
|
205
|
+
##
|
206
|
+
# Returns the absolute path to a directory under {#data_home}, creating
|
207
|
+
# it if it doesn't already exist.
|
208
|
+
#
|
209
|
+
# @param path [String] The relative path to the subdir within the base
|
210
|
+
# data directory.
|
211
|
+
# @return [String] The absolute path to the subdir.
|
212
|
+
# @raise [Errno::EEXIST] If a non-directory already exists there
|
213
|
+
#
|
214
|
+
def ensure_data_subdir(path)
|
215
|
+
ensure_subdir_internal(data_home, path)
|
216
|
+
end
|
217
|
+
|
218
|
+
##
|
219
|
+
# Returns the absolute path to a directory under {#config_home}, creating
|
220
|
+
# it if it doesn't already exist.
|
221
|
+
#
|
222
|
+
# @param path [String] The relative path to the subdir within the base
|
223
|
+
# config directory.
|
224
|
+
# @return [String] The absolute path to the subdir.
|
225
|
+
# @raise [Errno::EEXIST] If a non-directory already exists there
|
226
|
+
#
|
227
|
+
def ensure_config_subdir(path)
|
228
|
+
ensure_subdir_internal(config_home, path)
|
229
|
+
end
|
230
|
+
|
231
|
+
##
|
232
|
+
# Returns the absolute path to a directory under {#state_home}, creating
|
233
|
+
# it if it doesn't already exist.
|
234
|
+
#
|
235
|
+
# @param path [String] The relative path to the subdir within the base
|
236
|
+
# state directory.
|
237
|
+
# @return [String] The absolute path to the subdir.
|
238
|
+
# @raise [Errno::EEXIST] If a non-directory already exists there
|
239
|
+
#
|
240
|
+
def ensure_state_subdir(path)
|
241
|
+
ensure_subdir_internal(state_home, path)
|
242
|
+
end
|
243
|
+
|
244
|
+
##
|
245
|
+
# Returns the absolute path to a directory under {#cache_home}, creating
|
246
|
+
# it if it doesn't already exist.
|
247
|
+
#
|
248
|
+
# @param path [String] The relative path to the subdir within the base
|
249
|
+
# cache directory.
|
250
|
+
# @return [String] The absolute path to the subdir.
|
251
|
+
# @raise [Errno::EEXIST] If a non-directory already exists there
|
252
|
+
#
|
253
|
+
def ensure_cache_subdir(path)
|
254
|
+
ensure_subdir_internal(cache_home, path)
|
255
|
+
end
|
256
|
+
|
257
|
+
private
|
258
|
+
|
259
|
+
def validate_dir_env(name)
|
260
|
+
path = @env[name].to_s
|
261
|
+
!path.empty? && Compat.absolute_path?(path) ? path : nil
|
262
|
+
end
|
263
|
+
|
264
|
+
def validate_dirs_env(name)
|
265
|
+
validate_dirs(@env[name].to_s.split(::File::PATH_SEPARATOR))
|
266
|
+
end
|
267
|
+
|
268
|
+
def validate_dirs(paths)
|
269
|
+
paths = paths.find_all { |path| Compat.absolute_path?(path) }
|
270
|
+
paths.empty? ? nil : paths
|
271
|
+
end
|
272
|
+
|
273
|
+
def lookup_internal(dirs, path, types)
|
274
|
+
results = []
|
275
|
+
types = Array(types).map(&:to_s)
|
276
|
+
dirs.each do |dir|
|
277
|
+
to_check = ::File.join(dir, path)
|
278
|
+
stat = ::File.stat(to_check) rescue nil # rubocop:disable Style/RescueModifier
|
279
|
+
if stat&.readable? && (types.include?("any") || types.include?(stat.ftype))
|
280
|
+
results << to_check
|
281
|
+
end
|
282
|
+
end
|
283
|
+
results
|
284
|
+
end
|
285
|
+
|
286
|
+
def ensure_subdir_internal(base_dir, path)
|
287
|
+
path = ::File.join(base_dir, path)
|
288
|
+
::FileUtils.mkdir_p(path, mode: 0o700)
|
289
|
+
path
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
@@ -50,14 +50,21 @@ module Toys
|
|
50
50
|
end
|
51
51
|
alias to_s string
|
52
52
|
|
53
|
-
##
|
53
|
+
##
|
54
|
+
# Tests two wrappable strings for equality
|
55
|
+
# @param other [Object]
|
56
|
+
# @return [Boolean]
|
57
|
+
#
|
54
58
|
def ==(other)
|
55
59
|
return false unless other.is_a?(WrappableString)
|
56
60
|
other.fragments == fragments
|
57
61
|
end
|
58
62
|
alias eql? ==
|
59
63
|
|
60
|
-
##
|
64
|
+
##
|
65
|
+
# Returns a hash code for this object
|
66
|
+
# @return [Integer]
|
67
|
+
#
|
61
68
|
def hash
|
62
69
|
fragments.hash
|
63
70
|
end
|