toys-core 0.11.5 → 0.13.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 +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
|