atli 0.1.9 → 0.1.10

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.
@@ -0,0 +1,48 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ # Requirements
5
+ # =======================================================================
6
+
7
+ # Deps
8
+ # -----------------------------------------------------------------------
9
+
10
+ require 'nrser'
11
+ require 'nrser/labs/i8'
12
+
13
+
14
+ # Refinements
15
+ # =======================================================================
16
+
17
+ require 'nrser/refinements/types'
18
+ using NRSER::Types
19
+
20
+
21
+ # Namespace
22
+ # =======================================================================
23
+
24
+ class Thor
25
+ module Completion
26
+ module Bash
27
+
28
+ # Definitions
29
+ # =======================================================================
30
+
31
+ # Structre to hold Bash complete request parameters
32
+ class Request < I8::Struct.new(
33
+ cur: t.str,
34
+ prev: t.str,
35
+ cword: t.non_neg_int,
36
+ split: t.bool,
37
+ words: t.array( t.str )
38
+ )
39
+
40
+ end # class Request
41
+
42
+
43
+ # /Namespace
44
+ # =======================================================================
45
+
46
+ end # module Bash
47
+ end # module Completion
48
+ end # class Thor
@@ -0,0 +1,136 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ # Requirements
5
+ # ========================================================================
6
+
7
+ # Project / Package
8
+ # ------------------------------------------------------------------------
9
+
10
+ # Need to make sure {Thor} is loaded first in case a user file requires
11
+ # us here beforehand
12
+ require 'thor'
13
+
14
+
15
+ # Refinements
16
+ # =======================================================================
17
+
18
+ require 'nrser/refinements/types'
19
+ using NRSER::Types
20
+
21
+
22
+ # Namespace
23
+ # =======================================================================
24
+
25
+ class Thor
26
+ module Completion
27
+ module Bash
28
+
29
+
30
+ # Definitions
31
+ # =======================================================================
32
+
33
+ # {Thor} added as a {Thor.subcommand} to the includer of
34
+ # {Thor::Completion::Bash} to expose Bash completion endpoints.
35
+ #
36
+ class Subcmd < ::Thor
37
+
38
+ # Commands
39
+ # ========================================================================
40
+
41
+ desc 'complete -- CUR PREV CWORD SPLIT WORDS...',
42
+ "Provide Bash completions"
43
+
44
+ # Execute Bash completion.
45
+ #
46
+ # @param [Array] *_
47
+ # Ignored - input custom read from `ARGV`.
48
+ #
49
+ # @return [void]
50
+ # Never returns - manually calls `exit` when done.
51
+ #
52
+ def complete *_
53
+ # logger.level = :trace
54
+
55
+ logger.trace "Starting Bash complete...",
56
+ ARGV: ARGV,
57
+ args: args
58
+
59
+ args = ARGV.dup
60
+
61
+ args.shift while args[0] != '--'
62
+ args.shift
63
+
64
+ cur, prev, cword, split, *words = args
65
+
66
+ request = Request.new \
67
+ cur: cur, # options[:cur],
68
+ prev: prev, # options[:prev],
69
+ cword: cword.to_i, # options[:cword],
70
+ split: split.truthy?, # options[:split],
71
+ words: words
72
+
73
+ logger.trace "Bash complete Request loaded",
74
+ request: request
75
+
76
+ results = begin
77
+ self.class.target.bash_complete( request: request, index: 1 )
78
+ rescue StandardError => error
79
+ logger.error "Error raised processing Bash complete request",
80
+ { request: request },
81
+ error
82
+
83
+ []
84
+ end
85
+
86
+ joined = results.shelljoin
87
+
88
+ logger.trace "Sending Bash compelte response",
89
+ request: request,
90
+ results: results,
91
+ joined: joined
92
+
93
+ puts joined
94
+ exit true
95
+ end # #complete
96
+
97
+
98
+ desc 'setup',
99
+ "Source this output in your shell or profile to install."
100
+
101
+ long_desc <<~END
102
+ Prints Bash source code to STDOUT that hooks `complete` into the exe.
103
+ END
104
+
105
+ example "source <(#{ $0 } bash-complete setup)"
106
+
107
+ # Print Bash source code to hook into `complete` to `$stdout`.
108
+ #
109
+ # @return [nil]
110
+ #
111
+ def setup
112
+ bin = File.basename $0
113
+ name = bin.underscore
114
+
115
+ erb_src_path = ::Thor::ROOT.join 'support',
116
+ 'completion',
117
+ 'complete.inc.bash.erb'
118
+
119
+ erb_src = erb_src_path.read
120
+
121
+ bash_src = binding.erb erb_src
122
+
123
+ puts bash_src
124
+
125
+ nil
126
+ end # #setup
127
+
128
+ end # class Subcmd
129
+
130
+
131
+ # /Namespace
132
+ # =======================================================================
133
+
134
+ end # module Bash
135
+ end # module Completion
136
+ end # class Thor
@@ -0,0 +1,250 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+
5
+ # Refinements
6
+ # =======================================================================
7
+
8
+ require 'nrser/refinements/types'
9
+ using NRSER::Types
10
+
11
+
12
+ # Namespace
13
+ # =======================================================================
14
+
15
+ class Thor
16
+ module Completion
17
+ module Bash
18
+
19
+
20
+ # Definitions
21
+ # =======================================================================
22
+
23
+ # To be mixed in to {Thor}. It's all class methods at the moment.
24
+ #
25
+ # @todo
26
+ # Deal with that {Thor::Group} thing? I never use it...
27
+ #
28
+ module ThorMixin
29
+
30
+ # Methods to be mixed as class methods in to {Thor}.
31
+ #
32
+ module ClassMethods
33
+
34
+ # Handles Bash completion requests at the {Thor} *class* level.
35
+ #
36
+ # This consists of:
37
+ #
38
+ # 1. Finding the next word in the `request` at of after `index` that
39
+ # identifies next the command or subcommand.
40
+ #
41
+ # **NOTE** This is done by simply scanning over words that start
42
+ # with `-`, which will not work for options that take values as
43
+ # separate shell words.
44
+ #
45
+ # 2. Looking for an exact match command or subcommand to the word from
46
+ # (1), and passing processing off to the respective {Thor::Command}
47
+ # instance or {Thor::Base} subclass.
48
+ #
49
+ # 3. Or returning an array of "starts with" matches for available
50
+ # {Thor::Base::ClassMethods#all_commands} and any partial input.
51
+ #
52
+ # Checks against both {Thor::Command#name} ("method" name) and
53
+ # {Thor::Command#usage_name}, preferring usage name.
54
+ #
55
+ # @param [Request] request:
56
+ #
57
+ #
58
+ def bash_complete request:, index:
59
+ # logger.level = :trace
60
+
61
+ logger.trace __method__,
62
+ request: request,
63
+ index: index,
64
+ word: request.words[index]
65
+
66
+ # Index we'll incremenet as we scan past options
67
+ scan_index = index
68
+
69
+ while scan_index < request.cword &&
70
+ scan_index < request.words.length &&
71
+ request.words[scan_index].start_with?( '-' )
72
+ scan_index += 1
73
+ end
74
+
75
+ if scan_index == request.cword
76
+ return bash_complete_cur request: request
77
+ end
78
+
79
+ unless scan_index < request.words.length
80
+ # We ran out of words without hitting a command name or ending
81
+ # empty string (for which we would provide all commands as options)
82
+ #
83
+ # TODO In the future we can deal with half-written class options
84
+ # I guess? Maybe? But for now we just give up.
85
+ #
86
+ return [].tap { |results|
87
+ logger.trace "No command or empty string found",
88
+ results: []
89
+ }
90
+ end
91
+
92
+ # OK, we should have either '' or something we can match
93
+ match_word = request.words[scan_index]
94
+
95
+ # We have nothing, return all commands
96
+ if match_word == ''
97
+ return all_commands.values.map( &:usage_name ).tap { |results|
98
+ logger.trace "Empty match word, returning all commands",
99
+ results: results
100
+ }
101
+ end
102
+
103
+ # See what possibilities we have
104
+ possibilities = find_command_possibilities match_word.tr( '-', '_' )
105
+
106
+ case possibilities.length
107
+ when 0
108
+ # We couldn't match anything
109
+ logger.trace "Found no command possibilities",
110
+ match_word: match_word,
111
+ results: []
112
+
113
+ return []
114
+
115
+ when 1
116
+ # We have a unique match
117
+
118
+ logger.trace "Unique command matched",
119
+ match_word: match_word,
120
+ match: possibilities[0]
121
+
122
+ # pass to below
123
+
124
+ else
125
+ # There is more than one possbility, but we're trying to fill in
126
+ # something later on, so we're SO
127
+ return []
128
+ end
129
+
130
+ # See if we're got an extact match
131
+ cmd = all_commands[ possibilities[0].underscore ]
132
+
133
+ # Bump the index to the scan index + 1 to go past the word we just
134
+ # used to find `cmd`
135
+ index = scan_index + 1
136
+
137
+ # is it a subcommand?
138
+ if subcommand_classes.key? cmd.name
139
+ # It is, hand it off to there
140
+ subcommand_classes[cmd.name].bash_complete \
141
+ request: request,
142
+ index: index
143
+ else
144
+ cmd.bash_complete request: request, index: index, klass: self
145
+ end
146
+ end # #bash_complete
147
+
148
+
149
+
150
+ private
151
+ # ========================================================================
152
+
153
+ def bash_complete_cur request:
154
+ logger.trace "START #{ self.class.safe_name }.#{ __method__ }",
155
+ cur: request.cur
156
+
157
+ cur_method_name = if request.cur.start_with? '-'
158
+ request.cur
159
+ else
160
+ request.cur.tr( '-', '_' )
161
+ end
162
+
163
+ method_name_matches = find_command_possibilities( cur_method_name ).map &:to_s
164
+
165
+ logger.trace "Matched #{ method_name_matches.length } method name(s)",
166
+ matches: method_name_matches # ,
167
+ # all_method_names: all_commands.keys,
168
+ # map: map
169
+
170
+ case method_name_matches.length
171
+ when 0
172
+ return [].tap { |results|
173
+ logger.trace "No commands matched",
174
+ results: results
175
+ }
176
+
177
+ when 1
178
+ method_name = method_name_matches[0]
179
+
180
+ logger.trace "Found unique method name",
181
+ method_name: method_name
182
+
183
+ cmd = all_commands[ method_name ] || all_commands[ map[ method_name ].to_s ]
184
+
185
+ logger.trace "Got command",
186
+ command: cmd
187
+
188
+ cmd.names_by_format.each { |format, name|
189
+ if name.start_with?( request.cur )
190
+ return [ name ].tap { |results|
191
+ logger.trace \
192
+ "Prefix-matched cur against cmd's #{ format } name",
193
+ name_format: format,
194
+ results: results,
195
+ cmd: cmd
196
+ }
197
+ end
198
+ }
199
+
200
+ matching_mappings = map.map { |map_name, cmd_name|
201
+ map_name.to_s if cmd.name == cmd_name.to_s &&
202
+ map_name.to_s.start_with?( request.cur )
203
+ }.compact
204
+
205
+ return matching_mappings unless matching_mappings.empty?
206
+
207
+ return [ request.cur ]
208
+
209
+ else
210
+ # There is more than one possbility...
211
+
212
+ cmd_matches = method_name_matches.map { |method_name|
213
+ all_commands[ method_name ] || all_commands[ map[ method_name ].to_s ]
214
+ }.uniq
215
+
216
+ logger.trace "Unique command matches",
217
+ cmd_matches: cmd_matches
218
+
219
+ usage_name_results = cmd_matches.map( &:usage_name ).select { |usage_name|
220
+ usage_name.start_with? request.cur
221
+ }
222
+
223
+ return usage_name_results unless usage_name_results.empty?
224
+
225
+ return method_name_matches.select { |method_name|
226
+ method_name.start_with? request.cur
227
+ }
228
+ end
229
+ end # #bash_complete_cur
230
+
231
+ public # end private *****************************************************
232
+
233
+ end # module ClassMethods
234
+
235
+
236
+ # Hook to mix {ClassMethods} in on include.
237
+ #
238
+ def self.included base
239
+ base.extend ClassMethods
240
+ end
241
+
242
+ end # module ThorMixin
243
+
244
+
245
+ # /Namespace
246
+ # =======================================================================
247
+
248
+ end # module Bash
249
+ end # module Completion
250
+ end # class Thor
@@ -1,5 +1,12 @@
1
+ # Namespace
2
+ # ========================================================================
3
+
1
4
  class Thor
2
5
  module CoreExt
6
+
7
+ # Definitions
8
+ # ========================================================================
9
+
3
10
  # A hash with indifferent access and magic predicates.
4
11
  #
5
12
  # hash = HashWithIndifferentAccess.new 'foo' => 'bar', 'baz' => 'bee', 'force' => true
@@ -34,4 +41,11 @@ class HashWithIndifferentAccess < ::HashWithIndifferentAccess #:nodoc:
34
41
 
35
42
  public # end protected ***************************************************
36
43
 
37
- end; end; end
44
+ end
45
+
46
+
47
+ # /Namespace
48
+ # ========================================================================
49
+
50
+ end # module CoreExt
51
+ end # class Thor