wifi-wand 2.4.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.
data/exe/wifi-wand ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This script brings together several useful wifi-related functions.
4
+ #
5
+ # It is a bit of a kludge in that it calls Mac OS commands and uses
6
+ # the text output for its data. At some point I would like to replace
7
+ # that with system calls. Currently this script can break if Apple
8
+ # decides to modify the name, options, behavior, and/or format of its utilities.
9
+ #
10
+ # What would be *really* nice, would be for Apple to retrofit all
11
+ # system commands to optionally output JSON and/or YAML. Some offer XML, but that
12
+ # is not convenient to use.
13
+ #
14
+ # Mac OS commands currently used are: airport, ipconfig, networksetup, security.
15
+ #
16
+ # Author: keithrbennett (on Github, GMail, Twitter)
17
+ # I am available for Ruby development, troubleshooting, training, tutoring, etc.
18
+ #
19
+ # License: MIT License
20
+
21
+
22
+ require_relative '../lib/wifi-wand/main'
23
+
24
+ WifiWand::Main.new.call
data/lib/wifi-wand.rb ADDED
@@ -0,0 +1,3 @@
1
+ require_relative 'wifi-wand/version'
2
+
3
+ require_relative 'wifi-wand/main' # recursively requires the other files
@@ -0,0 +1,462 @@
1
+ require_relative 'version'
2
+ require_relative 'operating_systems'
3
+
4
+ module WifiWand
5
+
6
+ class CommandLineInterface
7
+
8
+ attr_reader :interactive_mode, :model, :open_resources, :options
9
+
10
+
11
+ class Command < Struct.new(:min_string, :max_string, :action); end
12
+
13
+
14
+ class OpenResource < Struct.new(:code, :resource, :description)
15
+
16
+ # Ex: "'ipw' (What is My IP)"
17
+ def help_string
18
+ "'#{code}' (#{description})"
19
+ end
20
+ end
21
+
22
+
23
+ class OpenResources < Array
24
+
25
+ def find_by_code(code)
26
+ detect { |resource| resource.code == code }
27
+ end
28
+
29
+ # Ex: "('ipc' (IP Chicken), 'ipw' (What is My IP), 'spe' (Speed Test))"
30
+ def help_string
31
+ map(&:help_string).join(', ')
32
+ end
33
+ end
34
+
35
+
36
+ class BadCommandError < RuntimeError
37
+ def initialize(error_message)
38
+ super
39
+ end
40
+ end
41
+
42
+ OPEN_RESOURCES = OpenResources.new([
43
+ OpenResource.new('ipc', 'https://ipchicken.com/', 'IP Chicken'),
44
+ OpenResource.new('ipw', 'https://www.whatismyip.com', 'What is My IP'),
45
+ OpenResource.new('spe', 'http://speedtest.net/', 'Speed Test'),
46
+ OpenResource.new('this', 'https://github.com/keithrbennett/wifiwand', 'wifi-wand Home Page'),
47
+ ])
48
+
49
+
50
+ # Help text to be used when requested by 'h' command, in case of unrecognized or nonexistent command, etc.
51
+ HELP_TEXT = "
52
+ Command Line Switches: [wifi-wand version #{WifiWand::VERSION}]
53
+
54
+ -o[i,j,p,y] - outputs data in inspect, JSON, puts, or YAML format when not in shell mode
55
+ -s - run in shell mode
56
+ -v - verbose mode (prints OS commands and their outputs)
57
+
58
+ Commands:
59
+
60
+ a[vail_nets] - array of names of the available networks
61
+ ci - connected to Internet (not just wifi on)?
62
+ co[nnect] network-name - turns wifi on, connects to network-name
63
+ cy[cle] - turns wifi off, then on, preserving network selection
64
+ d[isconnect] - disconnects from current network, does not turn off wifi
65
+ h[elp] - prints this help
66
+ i[nfo] - a hash of wifi-related information
67
+ l[s_avail_nets] - details about available networks
68
+ n[etwork_name] - name (SSID) of currently connected network
69
+ on - turns wifi on
70
+ of[f] - turns wifi off
71
+ pa[ssword] network-name - password for preferred network-name
72
+ pr[ef_nets] - preferred (not necessarily available) networks
73
+ q[uit] - exits this program (interactive shell mode only) (see also 'x')
74
+ r[m_pref_nets] network-name - removes network-name from the preferred networks list
75
+ (can provide multiple names separated by spaces)
76
+ ro[pen] - open resource (#{OPEN_RESOURCES.help_string})
77
+ t[ill] - returns when the desired Internet connection state is true. Options:
78
+ 1) 'on'/:on, 'off'/:off, 'conn'/:conn, or 'disc'/:disc
79
+ 2) wait interval, in seconds (optional, defaults to 0.5 seconds)
80
+ w[ifion] - is the wifi on?
81
+ x[it] - exits this program (interactive shell mode only) (see also 'q')
82
+
83
+ When in interactive shell mode:
84
+ * use quotes for string parameters such as method names.
85
+ * for pry commands, use prefix `%`.
86
+
87
+ "
88
+
89
+ def initialize(options)
90
+ @options = options
91
+ current_os = OperatingSystems.new.current_os
92
+ raise "Could not determine operating system" if current_os.nil?
93
+ @model = current_os.create_model(verbose_mode)
94
+ @interactive_mode = !!(options.interactive_mode)
95
+ run_shell if @interactive_mode
96
+ end
97
+
98
+
99
+ # Until command line option parsing is added, the only way to specify
100
+ # verbose mode is in the environment variable MAC_WIFI_OPTS.
101
+ def verbose_mode
102
+ options.verbose
103
+ end
104
+
105
+
106
+ def print_help
107
+ puts HELP_TEXT
108
+ end
109
+
110
+
111
+ # @return true if awesome_print is available (after requiring it), else false after requiring 'pp'.
112
+ # We'd like to use awesome_print if it is available, but not require it.
113
+ # So, we try to require it, but if that fails, we fall back to using pp (pretty print),
114
+ # which is included in Ruby distributions without the need to install a gem.
115
+ def awesome_print_available?
116
+ if @awesome_print_available.nil? # first time here
117
+ begin
118
+ require 'awesome_print'
119
+ @awesome_print_available = true
120
+ rescue LoadError
121
+ require 'pp'
122
+ @awesome_print_available = false
123
+ end
124
+ end
125
+
126
+ @awesome_print_available
127
+ end
128
+
129
+
130
+ def fancy_string(object)
131
+ awesome_print_available? ? object.ai : object.pretty_inspect
132
+ end
133
+
134
+
135
+ def fancy_puts(object)
136
+ puts fancy_string(object)
137
+ end
138
+ alias_method :fp, :fancy_puts
139
+
140
+
141
+ # Asserts that a command has been passed on the command line.
142
+ def validate_command_line
143
+ if ARGV.empty?
144
+ puts "Syntax is: #{__FILE__} [options] command [command_options]"
145
+ print_help
146
+ exit(-1)
147
+ end
148
+ end
149
+
150
+
151
+ # Pry will output the content of the method from which it was called.
152
+ # This small method exists solely to reduce the amount of pry's output
153
+ # that is not needed here.
154
+ def run_pry
155
+ binding.pry
156
+
157
+ # the seemingly useless line below is needed to avoid pry's exiting
158
+ # (see https://github.com/deivid-rodriguez/pry-byebug/issues/45)
159
+ _a = nil
160
+ end
161
+
162
+
163
+ # Runs a pry session in the context of this object.
164
+ # Commands and options specified on the command line can also be specified in the shell.
165
+ def run_shell
166
+ begin
167
+ require 'pry'
168
+ rescue LoadError
169
+ puts "The 'pry' gem and/or one of its prerequisites, required for running the shell, was not found." +
170
+ " Please `gem install pry` or, if necessary, `sudo gem install pry`."
171
+ exit(-1)
172
+ end
173
+
174
+ print_help
175
+
176
+ # Enable the line below if you have any problems with pry configuration being loaded
177
+ # that is messing up this runtime use of pry:
178
+ # Pry.config.should_load_rc = false
179
+
180
+ # Strangely, this is the only thing I have found that successfully suppresses the
181
+ # code context output, which is not useful here. Anyway, this will differentiate
182
+ # a pry command from a DSL command, which _is_ useful here.
183
+ Pry.config.command_prefix = '%'
184
+
185
+ run_pry
186
+ end
187
+
188
+
189
+ # For use by the shell; when typing a command and options, it is passed to process_command_line
190
+ def method_missing(method_name, *options)
191
+ method_name = method_name.to_s
192
+ method_exists = !! find_command_action(method_name)
193
+ if method_exists
194
+ process_command_line(method_name, options)
195
+ else
196
+ puts(%Q{"#{method_name}" is not a valid command or option. If you intend for this to be a string literal, use quotes or %q/Q{}.})
197
+ end
198
+ end
199
+
200
+
201
+ # Processes the command (ARGV[0]) and any relevant options (ARGV[1..-1]).
202
+ #
203
+ # CAUTION! In interactive mode, any strings entered (e.g. a network name) MUST
204
+ # be in a form that the Ruby interpreter will recognize as a string,
205
+ # i.e. single or double quotes, %q, %Q, etc.
206
+ # Otherwise it will assume it's a method name and pass it to method_missing!
207
+ def process_command_line(command, options)
208
+ action = find_command_action(command)
209
+ if action
210
+ action.(*options)
211
+ else
212
+ print_help
213
+ raise BadCommandError.new(
214
+ %Q{Unrecognized command. Command was "#{command}" and options were #{options.inspect}.})
215
+ end
216
+ end
217
+
218
+
219
+ def quit
220
+ if interactive_mode
221
+ exit(0)
222
+ else
223
+ puts "This command can only be run in shell mode."
224
+ end
225
+ end
226
+
227
+
228
+ def cmd_a
229
+ info = model.available_network_names
230
+ if interactive_mode
231
+ info
232
+ else
233
+ if post_processor
234
+ puts post_processor.(info)
235
+ else
236
+ puts model.wifi_on? \
237
+ ? "Available networks are:\n\n#{fancy_string(info)}" \
238
+ : "Wifi is off, cannot see available networks."
239
+ end
240
+ end
241
+ end
242
+
243
+
244
+ def cmd_ci
245
+ connected = model.connected_to_internet?
246
+ if interactive_mode
247
+ connected
248
+ else
249
+ puts (post_processor ? post_processor.(connected) : "Connected to Internet: #{connected}")
250
+ end
251
+ end
252
+
253
+
254
+ def cmd_co(network, password = nil)
255
+ model.connect(network, password)
256
+ end
257
+
258
+
259
+ def cmd_cy
260
+ model.cycle_network
261
+ end
262
+
263
+
264
+ def cmd_d
265
+ model.disconnect
266
+ end
267
+
268
+
269
+ def cmd_h
270
+ print_help
271
+ end
272
+
273
+
274
+ def cmd_i
275
+ info = model.wifi_info
276
+ if interactive_mode
277
+ info
278
+ else
279
+ if post_processor
280
+ puts post_processor.(info)
281
+ else
282
+ puts fancy_string(info)
283
+ end
284
+ end
285
+ end
286
+
287
+
288
+ def cmd_lsa
289
+ info = model.available_network_info
290
+ if interactive_mode
291
+ info
292
+ else
293
+ if post_processor
294
+ puts post_processor.(info)
295
+ else
296
+ message = model.wifi_on? ? fancy_string(info) : "Wifi is off, cannot see available networks."
297
+ puts(message)
298
+ end
299
+ end
300
+ end
301
+
302
+
303
+ def cmd_n
304
+ name = model.current_network
305
+ if interactive_mode
306
+ name
307
+ else
308
+ display_name = name ? name : '[none]'
309
+ puts (post_processor ? post_processor.(name) : %Q{Network (SSID) name: "#{display_name}"})
310
+ end
311
+ end
312
+
313
+
314
+ def cmd_of
315
+ model.wifi_off
316
+ end
317
+
318
+
319
+ def cmd_on
320
+ model.wifi_on
321
+ end
322
+
323
+
324
+ # Use Mac OS 'open' command line utility
325
+ def cmd_ro(*resource_codes)
326
+ resource_codes.each do |code|
327
+ resource = OPEN_RESOURCES.find_by_code(code)
328
+ if resource
329
+ model.open_resource(resource.resource)
330
+ end
331
+ end
332
+ end
333
+
334
+ def cmd_pa(network)
335
+ password = model.preferred_network_password(network)
336
+
337
+ if interactive_mode
338
+ password
339
+ else
340
+ if post_processor
341
+ puts post_processor.(password)
342
+ else
343
+ output = %Q{Preferred network "#{model.connected_network_name}" }
344
+ output << (password ? %Q{stored password is "#{password}".} : "has no stored password.")
345
+ puts output
346
+ end
347
+ end
348
+ end
349
+
350
+
351
+ def cmd_pr
352
+ networks = model.preferred_networks
353
+ if interactive_mode
354
+ networks
355
+ else
356
+ puts (post_processor ? post_processor.(networks) : fancy_string(networks))
357
+ end
358
+ end
359
+
360
+
361
+ def cmd_pu
362
+ `open https://www.whatismyip.com/`
363
+ end
364
+
365
+
366
+ def cmd_q
367
+ quit
368
+ end
369
+
370
+
371
+ def cmd_r(*options)
372
+ removed_networks = model.remove_preferred_networks(*options)
373
+ if interactive_mode
374
+ removed_networks
375
+ else
376
+ puts (post_processor ? post_processor.(removed_networks) : "Removed networks: #{removed_networks.inspect}")
377
+ end
378
+ end
379
+
380
+
381
+ def cmd_t(*options)
382
+ target_status = options[0].to_sym
383
+ wait_interval_in_secs = (options[1] ? Float(options[1]) : nil)
384
+ model.till(target_status, wait_interval_in_secs)
385
+ end
386
+
387
+
388
+ def cmd_w
389
+ on = model.wifi_on?
390
+ if interactive_mode
391
+ on
392
+ else
393
+ puts (post_processor ? post_processor.(on) : "Wifi on: #{on}")
394
+ end
395
+ end
396
+
397
+
398
+ def cmd_x
399
+ quit
400
+ end
401
+
402
+
403
+ def commands
404
+ @commands_ ||= [
405
+ Command.new('a', 'avail_nets', -> (*_options) { cmd_a }),
406
+ Command.new('ci', 'ci', -> (*_options) { cmd_ci }),
407
+ Command.new('co', 'connect', -> (*options) { cmd_co(*options) }),
408
+ Command.new('cy', 'cycle', -> (*_options) { cmd_cy }),
409
+ Command.new('d', 'disconnect', -> (*_options) { cmd_d }),
410
+ Command.new('h', 'help', -> (*_options) { cmd_h }),
411
+ Command.new('i', 'info', -> (*_options) { cmd_i }),
412
+ Command.new('l', 'ls_avail_nets', -> (*_options) { cmd_lsa }),
413
+ Command.new('n', 'network_name', -> (*_options) { cmd_n }),
414
+ Command.new('of', 'off', -> (*_options) { cmd_of }),
415
+ Command.new('on', 'on', -> (*_options) { cmd_on }),
416
+ Command.new('ro', 'ropen', -> (*options) { cmd_ro(*options) }),
417
+ Command.new('pa', 'password', -> (*options) { cmd_pa(*options) }),
418
+ Command.new('pr', 'pref_nets', -> (*_options) { cmd_pr }),
419
+ Command.new('q', 'quit', -> (*_options) { cmd_q }),
420
+ Command.new('r', 'rm_pref_nets', -> (*options) { cmd_r(*options) }),
421
+ Command.new('t', 'till', -> (*options) { cmd_t(*options) }),
422
+ Command.new('w', 'wifion', -> (*_options) { cmd_w }),
423
+ Command.new('x', 'xit', -> (*_options) { cmd_x })
424
+ ]
425
+ end
426
+
427
+
428
+ def find_command_action(command_string)
429
+ result = commands.detect do |cmd|
430
+ cmd.max_string.start_with?(command_string) \
431
+ && \
432
+ command_string.length >= cmd.min_string.length # e.g. 'c' by itself should not work
433
+ end
434
+
435
+ result ? result.action : nil
436
+ end
437
+
438
+
439
+ # If a post-processor has been configured (e.g. YAML or JSON), use it.
440
+ def post_process(object)
441
+ post_processor ? post_processor.(object) : object
442
+ end
443
+
444
+
445
+
446
+ def post_processor
447
+ options.post_processor
448
+ end
449
+
450
+
451
+ def call
452
+ validate_command_line
453
+ begin
454
+ process_command_line(ARGV[0], ARGV[1..-1])
455
+ rescue BadCommandError => error
456
+ separator_line = "! #{'-' * 75} !\n"
457
+ puts '' << separator_line << error.to_s << "\n" << separator_line
458
+ exit(-1)
459
+ end
460
+ end
461
+ end
462
+ end