Class: WifiWand::CommandLineInterface

Inherits:
Object
  • Object
show all
Defined in:
lib/wifi-wand/command_line_interface.rb

Defined Under Namespace

Classes: BadCommandError, Command, OpenResource, OpenResources

Constant Summary collapse

PROJECT_URL =
'https://github.com/keithrbennett/wifiwand'
OPEN_RESOURCES =
OpenResources.new([
    OpenResource.new('cap',  'https://captive.apple.com/',                'Portal Logins'),
    OpenResource.new('ipl',  'https://www.iplocation.net/',               'IP Location'),
    OpenResource.new('ipw',  'https://www.whatismyip.com',                'What is My IP'),
    OpenResource.new('libre','https://www.librespeed.org',                'LibreSpeed'),
    OpenResource.new('spe',  'http://speedtest.net/',                     'Speed Test'),
    OpenResource.new('this', 'https://github.com/keithrbennett/wifiwand', 'wifi-wand home page'),
])
HELP_TEXT =

Help text to be used when requested by ‘h’ command, in case of unrecognized or nonexistent command, etc.

"
Command Line Switches:                    [wifi-wand version #{WifiWand::VERSION} at https://github.com/keithrbennett/wifiwand]

-o {i,j,k,p,y}            - outputs data in inspect, JSON, pretty JSON, puts, or YAML format when not in shell mode
-p wifi_interface_name    - override automatic detection of interface name with this name
-s                        - run in shell mode
-v                        - verbose mode (prints OS commands and their outputs)

Commands:

a[vail_nets]              - array of names of the available networks
ci                        - connected to Internet (not just wifi on)?
co[nnect] network-name    - turns wifi on, connects to network-name
cy[cle]                   - turns wifi off, then on, preserving network selection
d[isconnect]              - disconnects from current network, does not turn off wifi
f[orget] name1 [..name_n] - removes network-name(s) from the preferred networks list
                            in interactive mode, can be a single array of names, e.g. returned by `pref_nets`
h[elp]                    - prints this help
i[nfo]                    - a hash of wifi-related information
na[meservers]             - nameservers: 'show' or no arg to show, 'clear' to clear,
                            or IP addresses to set, e.g. '9.9.9.9  8.8.8.8'
ne[twork_name]            - name (SSID) of currently connected network
on                        - turns wifi on
of[f]                     - turns wifi off
pa[ssword] network-name   - password for preferred network-name
pr[ef_nets]               - preferred (saved) networks
q[uit]                    - exits this program (interactive shell mode only) (see also 'x')
ro[pen]                   - open resource (#{OPEN_RESOURCES.help_string})
t[ill]                    - returns when the desired Internet connection state is true. Options:
                            1) 'on'/:on, 'off'/:off, 'conn'/:conn, or 'disc'/:disc
                            2) wait interval between tests, in seconds (optional, defaults to 0.5 seconds)
w[ifi_on]                 - is the wifi on?
x[it]                     - exits this program (interactive shell mode only) (see also 'q')

When in interactive shell mode:
  * remember to quote string literals.
  * for pry commands, use prefix `%`.

"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ CommandLineInterface

Returns a new instance of CommandLineInterface.



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/wifi-wand/command_line_interface.rb', line 97

def initialize(options)
  current_os = OperatingSystems.new.current_os
  if current_os.nil?
    puts "This application currently runs only on Mac OS."
    exit(1)
  end

  @options = options

  model_options = OpenStruct.new({
    verbose:        options.verbose,
    wifi_interface: options.wifi_interface
  })

  @model = current_os.create_model(model_options)
  @interactive_mode = !!(options.interactive_mode)
  run_shell if @interactive_mode
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *method_args) ⇒ Object

For use by the shell when the user types the DSL commands



181
182
183
184
185
186
187
# File 'lib/wifi-wand/command_line_interface.rb', line 181

def method_missing(method_name, *method_args)
  attempt_command_action(method_name.to_s, *method_args) do
    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{}.')
  end
end

Instance Attribute Details

#interactive_modeObject (readonly)

Returns the value of attribute interactive_mode.



11
12
13
# File 'lib/wifi-wand/command_line_interface.rb', line 11

def interactive_mode
  @interactive_mode
end

#modelObject (readonly)

Returns the value of attribute model.



11
12
13
# File 'lib/wifi-wand/command_line_interface.rb', line 11

def model
  @model
end

#open_resourcesObject (readonly)

Returns the value of attribute open_resources.



11
12
13
# File 'lib/wifi-wand/command_line_interface.rb', line 11

def open_resources
  @open_resources
end

#optionsObject (readonly)

Returns the value of attribute options.



11
12
13
# File 'lib/wifi-wand/command_line_interface.rb', line 11

def options
  @options
end

Instance Method Details

#attempt_command_action(command, *args, &error_handler_block) ⇒ Object

Look up the command name and, if found, run it. If not, execute the passed block.



168
169
170
171
172
173
174
175
176
177
# File 'lib/wifi-wand/command_line_interface.rb', line 168

def attempt_command_action(command, *args, &error_handler_block)
  action = find_command_action(command)

  if action
    action.(*args)
  else
    error_handler_block.call
    nil
  end
end

#callObject



461
462
463
464
465
466
467
468
469
470
471
472
# File 'lib/wifi-wand/command_line_interface.rb', line 461

def call
  validate_command_line
  begin
    # By this time, the Main class has removed the command line options, and all that is left
    # in ARGV is the commands and their options.
    process_command_line
  rescue BadCommandError => error
    separator_line = "! #{'-' * 75} !\n"
    puts '' << separator_line << error.to_s << "\n" << separator_line
    exit(-1)
  end
end

#cmd_aObject



214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/wifi-wand/command_line_interface.rb', line 214

def cmd_a
  info = model.available_network_names
  if interactive_mode
    info
  else
    if post_processor
      puts post_processor.(info)
    else
      puts model.wifi_on? \
          ? "Available networks are:\n\n#{fancy_string(info)}" \
          : "Wifi is off, cannot see available networks."
    end
  end
end

#cmd_ciObject



230
231
232
233
234
235
236
237
# File 'lib/wifi-wand/command_line_interface.rb', line 230

def cmd_ci
  connected = model.connected_to_internet?
  if interactive_mode
    connected
  else
    puts (post_processor ? post_processor.(connected) : "Connected to Internet: #{connected}")
  end
end

#cmd_co(network, password = nil) ⇒ Object



240
241
242
# File 'lib/wifi-wand/command_line_interface.rb', line 240

def cmd_co(network, password = nil)
  model.connect(network, password)
end

#cmd_cyObject



245
246
247
# File 'lib/wifi-wand/command_line_interface.rb', line 245

def cmd_cy
  model.cycle_network
end

#cmd_dObject



250
251
252
# File 'lib/wifi-wand/command_line_interface.rb', line 250

def cmd_d
  model.disconnect
end

#cmd_f(*options) ⇒ Object



381
382
383
384
385
386
387
388
# File 'lib/wifi-wand/command_line_interface.rb', line 381

def cmd_f(*options)
  removed_networks = model.remove_preferred_networks(*options)
  if interactive_mode
    removed_networks
  else
    puts (post_processor ? post_processor.(removed_networks) : "Removed networks: #{removed_networks.inspect}")
  end
end

#cmd_hObject



255
256
257
# File 'lib/wifi-wand/command_line_interface.rb', line 255

def cmd_h
  print_help
end

#cmd_iObject



260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/wifi-wand/command_line_interface.rb', line 260

def cmd_i
  info = model.wifi_info
  if interactive_mode
    info
  else
    if post_processor
      puts post_processor.(info)
    else
      puts fancy_string(info)
    end
  end
end

#cmd_na(*args) ⇒ Object

Performs nameserver functionality.

Parameters:

  • subcommandornoargtoget,toclear,andanarrayofIPaddressestoset ('get''clear')

    ubcommand ‘get’ or no arg to get, ‘clear’ to clear, and an array of IP addresses to set



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/wifi-wand/command_line_interface.rb', line 275

def cmd_na(*args)
  subcommand = if args.empty? || args.first.to_sym == :get
    :get
  elsif args.first.to_sym == :clear
    :clear
  else
    :put
  end

  case(subcommand)
    when :get
      current_nameservers = model.nameservers_using_networksetup
      if interactive_mode
        current_nameservers
      else
        if post_processor
          puts post_processor.(current_nameservers)
        else
          current_nameservers_as_string = current_nameservers.empty? ? "[None]" : current_nameservers.join(', ')
          puts "Nameservers: #{current_nameservers_as_string}"
        end
      end
    when :clear
      model.set_nameservers(:clear)
    when :put
      new_nameservers = args
      model.set_nameservers(new_nameservers)
  end
end

#cmd_neObject



306
307
308
309
310
311
312
313
314
# File 'lib/wifi-wand/command_line_interface.rb', line 306

def cmd_ne
  name = model.connected_network_name
  if interactive_mode
    name
  else
    display_name = name ? name : '[none]'
    puts (post_processor ? post_processor.(name) : %Q{Network (SSID) name: "#{display_name}"})
  end
end

#cmd_ofObject



317
318
319
# File 'lib/wifi-wand/command_line_interface.rb', line 317

def cmd_of
  model.wifi_off
end

#cmd_onObject



322
323
324
# File 'lib/wifi-wand/command_line_interface.rb', line 322

def cmd_on
  model.wifi_on
end

#cmd_pa(network) ⇒ Object



349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'lib/wifi-wand/command_line_interface.rb', line 349

def cmd_pa(network)
  password = model.preferred_network_password(network)

  if interactive_mode
    password
  else
    if post_processor
      puts post_processor.(password)
    else
      output =  %Q{Preferred network "#{model.connected_network_name}" }
      output << (password ? %Q{stored password is "#{password}".} : "has no stored password.")
      puts output
    end
  end
end

#cmd_prObject



366
367
368
369
370
371
372
373
# File 'lib/wifi-wand/command_line_interface.rb', line 366

def cmd_pr
  networks = model.preferred_networks
  if interactive_mode
    networks
  else
    puts (post_processor ? post_processor.(networks) : fancy_string(networks))
  end
end

#cmd_qObject



376
377
378
# File 'lib/wifi-wand/command_line_interface.rb', line 376

def cmd_q
  quit
end

#cmd_ro(*resource_codes) ⇒ Object

Use Mac OS ‘open’ command line utility



328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/wifi-wand/command_line_interface.rb', line 328

def cmd_ro(*resource_codes)
  if resource_codes.empty?
    puts "Please specify a resource to open:\n #{OPEN_RESOURCES.help_string.gsub(',', "\n")}"
    return
  end

  resource_codes.each do |code|
    code = code.to_s  # accommodate conversion of parameter from other types, esp. symbols
    resource = OPEN_RESOURCES.find_by_code(code)
    if resource
      if code == 'spe' && Dir.exist?('/Applications/Speedtest.app/')
        model.open_application('Speedtest')
      else
        model.open_resource(resource.resource)
      end
    end
  end
  nil
end

#cmd_t(*options) ⇒ Object



391
392
393
394
395
# File 'lib/wifi-wand/command_line_interface.rb', line 391

def cmd_t(*options)
  target_status = options[0].to_sym
  wait_interval_in_secs = (options[1] ? Float(options[1]) : nil)
  model.till(target_status, wait_interval_in_secs)
end

#cmd_wObject



398
399
400
401
402
403
404
405
# File 'lib/wifi-wand/command_line_interface.rb', line 398

def cmd_w
  on = model.wifi_on?
  if interactive_mode
    on
  else
    puts (post_processor ? post_processor.(on) : "Wifi on: #{on}")
  end
end

#cmd_xObject



408
409
410
# File 'lib/wifi-wand/command_line_interface.rb', line 408

def cmd_x
  quit
end

#commandsObject



413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
# File 'lib/wifi-wand/command_line_interface.rb', line 413

def commands
  @commands_ ||= [
      Command.new('a',   'avail_nets',    -> (*_options) { cmd_a             }),
      Command.new('ci',  'ci',            -> (*_options) { cmd_ci            }),
      Command.new('co',  'connect',       -> (*options)  { cmd_co(*options)  }),
      Command.new('cy',  'cycle',         -> (*_options) { cmd_cy            }),
      Command.new('d',   'disconnect',    -> (*_options) { cmd_d             }),
      Command.new('f',   'forget',        -> (*options)  { cmd_f(*options)   }),
      Command.new('h',   'help',          -> (*_options) { cmd_h             }),
      Command.new('i',   'info',          -> (*_options) { cmd_i             }),
      Command.new('l',   'ls_avail_nets', -> (*_options) { cmd_l             }),
      Command.new('na',  'nameservers',   -> (*options)  { cmd_na(*options)  }),
      Command.new('ne',  'network_name',  -> (*_options) { cmd_ne            }),
      Command.new('of',  'off',           -> (*_options) { cmd_of            }),
      Command.new('on',  'on',            -> (*_options) { cmd_on            }),
      Command.new('ro',  'ropen',         -> (*options)  { cmd_ro(*options)  }),
      Command.new('pa',  'password',      -> (*options)  { cmd_pa(*options)  }),
      Command.new('pr',  'pref_nets',     -> (*_options) { cmd_pr            }),
      Command.new('q',   'quit',          -> (*_options) { cmd_q             }),
      Command.new('t',   'till',          -> (*options)  { cmd_t(*options)   }),
      Command.new('u',   'url',           -> (*_options) { PROJECT_URL       }),
      Command.new('w',   'wifi_on',       -> (*_options) { cmd_w             }),
      Command.new('x',   'xit',           -> (*_options) { cmd_x             })
  ]
end

#fancy_puts(object) ⇒ Object Also known as: fp



132
133
134
# File 'lib/wifi-wand/command_line_interface.rb', line 132

def fancy_puts(object)
  puts fancy_string(object)
end

#fancy_string(object) ⇒ Object



127
128
129
# File 'lib/wifi-wand/command_line_interface.rb', line 127

def fancy_string(object)
  object.awesome_inspect
end

#find_command_action(command_string) ⇒ Object



440
441
442
443
444
445
446
447
# File 'lib/wifi-wand/command_line_interface.rb', line 440

def find_command_action(command_string)
  result = commands.detect do |cmd|
    cmd.max_string.start_with?(command_string) \
    && \
    command_string.length >= cmd.min_string.length  # e.g. 'c' by itself should not work
  end
  result ? result.action : nil
end

#post_process(object) ⇒ Object

If a post-processor has been configured (e.g. YAML or JSON), use it.



451
452
453
# File 'lib/wifi-wand/command_line_interface.rb', line 451

def post_process(object)
  post_processor ? post_processor.(object) : object
end

#post_processorObject



456
457
458
# File 'lib/wifi-wand/command_line_interface.rb', line 456

def post_processor
  options.post_processor
end


122
123
124
# File 'lib/wifi-wand/command_line_interface.rb', line 122

def print_help
  puts HELP_TEXT
end

#process_command_lineObject

Processes the command (ARGV) and any relevant options (ARGV).

CAUTION! In interactive mode, any strings entered (e.g. a network name) MUST be in a form that the Ruby interpreter will recognize as a string, i.e. single or double quotes, %q, %Q, etc. Otherwise it will assume it’s a method name and pass it to method_missing!



196
197
198
199
200
201
202
# File 'lib/wifi-wand/command_line_interface.rb', line 196

def process_command_line
  attempt_command_action(ARGV[0], *ARGV[1..-1]) do
    print_help
    raise BadCommandError.new(
        %Q{! Unrecognized command. Command was "#{ARGV.first.inspect}" and options were #{ARGV[1..-1].inspect}.})
  end
end

#quitObject



205
206
207
208
209
210
211
# File 'lib/wifi-wand/command_line_interface.rb', line 205

def quit
  if interactive_mode
    exit(0)
  else
    puts "This command can only be run in shell mode."
  end
end

#run_shellObject

Runs a pry session in the context of this object. Commands and options specified on the command line can also be specified in the shell.



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/wifi-wand/command_line_interface.rb', line 150

def run_shell
  print_help
  require 'pry'

  # Enable the line below if you have any problems with pry configuration being loaded
  # that is messing up this runtime use of pry:
  # Pry.config.should_load_rc = false

  # Strangely, this is the only thing I have found that successfully suppresses the
  # code context output, which is not useful here. Anyway, this will differentiate
  # a pry command from a DSL command, which _is_ useful here.
  Pry.config.command_prefix = '%'

  binding.pry
end

#validate_command_lineObject

Asserts that a command has been passed on the command line.



139
140
141
142
143
144
145
# File 'lib/wifi-wand/command_line_interface.rb', line 139

def validate_command_line
  if ARGV.empty?
    puts "Syntax is: #{$0} [options] command [command_options]"
    print_help
    exit(-1)
  end
end

#verbose_modeObject



117
118
119
# File 'lib/wifi-wand/command_line_interface.rb', line 117

def verbose_mode
  options.verbose
end