wifi-wand 2.4.2 → 2.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c55ac27fd01ee88c6ff0551ca7ff6a57b577531938a5d7bd1c0a2ef976886a5b
4
- data.tar.gz: c6ae8d9baa9970467bf804506e6922427687d968ef19dd23f96144767d337a62
3
+ metadata.gz: c9c498e8044e8913348ab143847be13dd0e1683d72c0e26448ae63d817a0675d
4
+ data.tar.gz: 440847dc2ddc905172a60cf090b863669ec5fef41d5a23f93e7540326d257583
5
5
  SHA512:
6
- metadata.gz: 06c3a288948f71a3d370f1f3411b822fc43fccde8b8242a595ee1e8996e4df6d13c1e42bb9837f227d79da62217bb440430bf67ef41908e67f87d96aaa22ef9d
7
- data.tar.gz: 519f272c7a5cf9a0d5c196e80baf65fb17afb229ebdafece9063afda8705e61d1a4af68561b1a3c90132e2846cd7691eb7967b461750e6d7e065950569295eff
6
+ metadata.gz: 5ab2bfebaf3c9dd5ad50e8cc7f9aeaa96c1f9799c504153616e920e278419b4f9d004f32083997f1f5c9cc0794557d6b5af093f2b483f519c1d4691b25346772
7
+ data.tar.gz: 48cc73787df1b6d8a5cb0aa641047bd7b796c9e2c7cf8b25d112427122c780101d7847d3b380cb704ac2f173eb3e8ba9b294295e26e6cca5ee299b93cc95a449
data/README.md CHANGED
@@ -28,9 +28,12 @@ output at the time of this writing:
28
28
  ```
29
29
  $ wifi-wand -h
30
30
 
31
+
32
+
31
33
  Command Line Switches: [wifi-wand version 2.4.2]
32
34
 
33
- -o[i,j,p,y] - outputs data in inspect, JSON, puts, or YAML format when not in shell mode
35
+ -o {i,j,p,y} - outputs data in inspect, JSON, puts, or YAML format when not in shell mode
36
+ -p wifi_port_name - override automatic detection of port name with this name
34
37
  -s - run in shell mode
35
38
  -v - verbose mode (prints OS commands and their outputs)
36
39
 
data/RELEASE_NOTES.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## v2.5.0
2
+
3
+ * Add limited support for nonstandard wifi devices (https://github.com/keithrbennett/wifiwand/issues/6).
4
+
5
+
1
6
  ## v2.4.2
2
7
 
3
8
  * Fix test.
@@ -5,7 +10,7 @@
5
10
 
6
11
  ## v2.4.1
7
12
 
8
- * Fix bug: undefined local variable or method `connected_network_name'
13
+ * Fix bug: undefined local variable or method `connected_network_name'.
9
14
 
10
15
 
11
16
  ## v2.4.0
data/exe/wifi-wand CHANGED
@@ -1,24 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
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
3
  require_relative '../lib/wifi-wand/main'
23
4
 
24
5
  WifiWand::Main.new.call
@@ -1,5 +1,6 @@
1
- require_relative 'version'
2
1
  require_relative 'operating_systems'
2
+ require 'ostruct'
3
+ require_relative 'version'
3
4
 
4
5
  module WifiWand
5
6
 
@@ -51,7 +52,8 @@ class CommandLineInterface
51
52
  HELP_TEXT = "
52
53
  Command Line Switches: [wifi-wand version #{WifiWand::VERSION}]
53
54
 
54
- -o[i,j,p,y] - outputs data in inspect, JSON, puts, or YAML format when not in shell mode
55
+ -o {i,j,k,p,y} - outputs data in inspect, JSON, pretty JSON, puts, or YAML format when not in shell mode
56
+ -p wifi_port_name - override automatic detection of port name with this name
55
57
  -s - run in shell mode
56
58
  -v - verbose mode (prints OS commands and their outputs)
57
59
 
@@ -90,7 +92,11 @@ When in interactive shell mode:
90
92
  @options = options
91
93
  current_os = OperatingSystems.new.current_os
92
94
  raise "Could not determine operating system" if current_os.nil?
93
- @model = current_os.create_model(verbose_mode)
95
+ model_options = OpenStruct.new({
96
+ verbose: options.verbose,
97
+ wifi_port: options.wifi_port
98
+ })
99
+ @model = current_os.create_model(model_options)
94
100
  @interactive_mode = !!(options.interactive_mode)
95
101
  run_shell if @interactive_mode
96
102
  end
@@ -141,7 +147,7 @@ When in interactive shell mode:
141
147
  # Asserts that a command has been passed on the command line.
142
148
  def validate_command_line
143
149
  if ARGV.empty?
144
- puts "Syntax is: #{__FILE__} [options] command [command_options]"
150
+ puts "Syntax is: #{$0} [options] command [command_options]"
145
151
  print_help
146
152
  exit(-1)
147
153
  end
@@ -285,7 +291,7 @@ When in interactive shell mode:
285
291
  end
286
292
 
287
293
 
288
- def cmd_lsa
294
+ def cmd_l
289
295
  info = model.available_network_info
290
296
  if interactive_mode
291
297
  info
@@ -409,7 +415,7 @@ When in interactive shell mode:
409
415
  Command.new('d', 'disconnect', -> (*_options) { cmd_d }),
410
416
  Command.new('h', 'help', -> (*_options) { cmd_h }),
411
417
  Command.new('i', 'info', -> (*_options) { cmd_i }),
412
- Command.new('l', 'ls_avail_nets', -> (*_options) { cmd_lsa }),
418
+ Command.new('l', 'ls_avail_nets', -> (*_options) { cmd_l }),
413
419
  Command.new('n', 'network_name', -> (*_options) { cmd_n }),
414
420
  Command.new('of', 'off', -> (*_options) { cmd_of }),
415
421
  Command.new('on', 'on', -> (*_options) { cmd_on }),
@@ -1,66 +1,69 @@
1
+ require 'json'
2
+ require 'optparse'
3
+ require 'ostruct'
4
+ require 'yaml'
5
+
1
6
  require_relative 'command_line_interface'
2
7
  require_relative 'operating_systems'
3
8
 
4
9
 
5
10
  module WifiWand
11
+ class Main
6
12
 
7
- require 'json'
8
- require 'optparse'
9
- require 'ostruct'
10
- require 'yaml'
11
-
12
- class Main
13
-
14
- # Parses the command line with Ruby's internal 'optparse'.
15
- # Looks for "-v" flag to set verbosity to true.
16
- # optparse removes what it processes from ARGV, which simplifies our command parsing.
17
- def parse_command_line
18
- options = OpenStruct.new
19
- OptionParser.new do |parser|
20
- parser.on("-v", "--[no-]verbose", "Run verbosely") do |v|
21
- options.verbose = v
22
- end
13
+ # Parses the command line with Ruby's internal 'optparse'.
14
+ # optparse removes what it processes from ARGV, which simplifies our command parsing.
15
+ def parse_command_line
16
+ options = OpenStruct.new
23
17
 
24
- parser.on("-s", "--shell", "Start interactive shell") do |v|
25
- options.interactive_mode = true
26
- end
18
+ OptionParser.new do |parser|
19
+ parser.on("-v", "--[no-]verbose", "Run verbosely") do |v|
20
+ options.verbose = v
21
+ end
27
22
 
28
- parser.on("-o", "--output_format FORMAT", "Format output data") do |v|
23
+ parser.on("-s", "--shell", "Start interactive shell") do |v|
24
+ options.interactive_mode = true
25
+ end
29
26
 
30
- transformers = {
31
- 'i' => ->(object) { object.inspect },
32
- 'j' => ->(object) { JSON.pretty_generate(object) },
33
- 'p' => ->(object) { sio = StringIO.new; sio.puts(object); sio.string },
34
- 'y' => ->(object) { object.to_yaml }
35
- }
27
+ parser.on("-o", "--output_format FORMAT", "Format output data") do |v|
36
28
 
37
- choice = v[0].downcase
29
+ formatters = {
30
+ 'i' => ->(object) { object.inspect },
31
+ 'j' => ->(object) { object.to_json },
32
+ 'k' => ->(object) { JSON.pretty_generate(object) },
33
+ 'p' => ->(object) { sio = StringIO.new; sio.puts(object); sio.string },
34
+ 'y' => ->(object) { object.to_yaml }
35
+ }
38
36
 
39
- unless transformers.keys.include?(choice)
40
- raise %Q{Output format "#{choice}" not in list of available formats} +
41
- " (#{transformers.keys.inspect})."
42
- end
37
+ choice = v[0].downcase
43
38
 
44
- options.post_processor = transformers[choice]
39
+ unless formatters.keys.include?(choice)
40
+ raise %Q{Output format "#{choice}" not in list of available formats} +
41
+ " (#{formatters.keys.inspect})."
45
42
  end
46
43
 
47
- parser.on("-h", "--help", "Show help") do |_help_requested|
48
- ARGV << 'h' # pass on the request to the command processor
49
- end
50
- end.parse!
51
- options
52
- end
44
+ options.post_processor = formatters[choice]
45
+ end
53
46
 
47
+ parser.on("-p", "--wifi-port PORT", "WiFi port name") do |v|
48
+ options.wifi_port = v
49
+ end
54
50
 
55
- def call
56
- operating_systems = OperatingSystems.new
57
- unless operating_systems.current_id == :mac
58
- raise "OS not in supported list of #{operating_systems.supported_os_names.inspect}"
51
+ parser.on("-h", "--help", "Show help") do |_help_requested|
52
+ ARGV << 'h' # pass on the request to the command processor
59
53
  end
54
+ end.parse!
55
+ options
56
+ end
57
+
60
58
 
61
- options = parse_command_line
59
+ def call
60
+ options = parse_command_line
62
61
 
62
+ begin
63
63
  WifiWand::CommandLineInterface.new(options).call
64
+ rescue => e
65
+ puts "Error: #{e.message}"
64
66
  end
65
67
  end
68
+ end
66
69
  end
@@ -6,6 +6,8 @@ module WifiWand
6
6
 
7
7
  class BaseModel
8
8
 
9
+ attr_accessor :wifi_port, :verbose_mode
10
+
9
11
  class OsCommandError < RuntimeError
10
12
  attr_reader :exitstatus, :command, :text
11
13
 
@@ -14,22 +16,39 @@ class BaseModel
14
16
  @command = command
15
17
  @text = text
16
18
  end
19
+
20
+ def to_s
21
+ "#{self.class.name}: Error code #{exitstatus}, command = #{command}, text = #{text}"
22
+ end
23
+
24
+ def to_h
25
+ { exitstatus: exitstatus, command: command, text: text }
26
+ end
17
27
  end
18
28
 
19
29
 
20
- def initialize(verbose = false)
21
- @verbose_mode = verbose
30
+ def initialize(options)
31
+ @verbose_mode = options.verbose
32
+ if options.wifi_port && (! is_wifi_port?(options.wifi_port))
33
+ raise "#{options.wifi_port} is not a Wi-Fi interface."
34
+ end
35
+ @wifi_port = options.wifi_port
22
36
  end
23
37
 
24
38
 
25
- def run_os_command(command)
39
+ def run_os_command(command, raise_on_error = true)
40
+
26
41
  output = `#{command} 2>&1` # join stderr with stdout
27
- if $?.exitstatus != 0
42
+
43
+ if $?.exitstatus != 0 && raise_on_error
28
44
  raise OsCommandError.new($?.exitstatus, command, output)
29
45
  end
46
+
30
47
  if @verbose_mode
31
- puts "\n\n#{'-' * 79}\nCommand: #{command}\n\nOutput:\n#{output}#{'-' * 79}\n\n"
48
+ puts "\n\n#{'-' * 79}\nCommand: #{command}\n\n"
49
+ puts "#{output}#{'-' * 79}\n\n"
32
50
  end
51
+
33
52
  output
34
53
  end
35
54
 
@@ -246,5 +265,10 @@ class BaseModel
246
265
  nameservers = unique_nameserver_lines.map { |line| line.split(' : ').last.strip }
247
266
  nameservers
248
267
  end
268
+
269
+
270
+ def wifi_port
271
+ @wifi_port ||= detect_wifi_port
272
+ end
249
273
  end
250
274
  end
@@ -1,3 +1,4 @@
1
+ require 'ostruct'
1
2
  require 'shellwords'
2
3
 
3
4
  require_relative 'base_model'
@@ -8,31 +9,31 @@ class MacOsModel < BaseModel
8
9
 
9
10
  AIRPORT_CMD = '/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport'
10
11
 
11
- def initialize(verbose = false)
12
+ # Takes an OpenStruct containing options such as verbose mode and port name.
13
+ def initialize(options = OpenStruct.new)
12
14
  super
13
15
  end
14
16
 
15
17
 
16
18
  # Identifies the (first) wireless network hardware port in the system, e.g. en0 or en1
17
- def wifi_hardware_port
18
- @wifi_hardware_port ||= begin
19
- lines = run_os_command("networksetup -listallhardwareports").split("\n")
20
- # Produces something like this:
21
- # Hardware Port: Wi-Fi
22
- # Device: en0
23
- # Ethernet Address: ac:bc:32:b9:a9:9d
24
- #
25
- # Hardware Port: Bluetooth PAN
26
- # Device: en3
27
- # Ethernet Address: ac:bc:32:b9:a9:9e
28
- wifi_port_line_num = (0...lines.size).detect do |index|
29
- /: Wi-Fi$/.match(lines[index])
30
- end
31
- if wifi_port_line_num.nil?
32
- raise %Q{Wifi port (e.g. "en0") not found in output of: networksetup -listallhardwareports}
33
- else
34
- lines[wifi_port_line_num + 1].split(': ').last
35
- end
19
+ # This may not detect wifi ports with nonstandard names, such as USB wifi devices.
20
+ def detect_wifi_port
21
+ lines = run_os_command("networksetup -listallhardwareports").split("\n")
22
+ # Produces something like this:
23
+ # Hardware Port: Wi-Fi
24
+ # Device: en0
25
+ # Ethernet Address: ac:bc:32:b9:a9:9d
26
+ #
27
+ # Hardware Port: Bluetooth PAN
28
+ # Device: en3
29
+ # Ethernet Address: ac:bc:32:b9:a9:9e
30
+ wifi_port_line_num = (0...lines.size).detect do |index|
31
+ /: Wi-Fi$/.match(lines[index])
32
+ end
33
+ if wifi_port_line_num.nil?
34
+ raise %Q{Wifi port (e.g. "en0") not found in output of: networksetup -listallhardwareports}
35
+ else
36
+ lines[wifi_port_line_num + 1].split(': ').last
36
37
  end
37
38
  end
38
39
 
@@ -41,6 +42,14 @@ class MacOsModel < BaseModel
41
42
  # For some reason, this often returns no results, so I've put the operation in a loop.
42
43
  # I was unable to detect a sort strategy in the airport utility's output, so I sort
43
44
  # the lines alphabetically, to show duplicates and for easier lookup.
45
+ #
46
+ # Sample Output:
47
+ #
48
+ # => ["SSID BSSID RSSI CHANNEL HT CC SECURITY (auth/unicast/group)",
49
+ # "ByCO-U00tRzUzMEg 64:6c:b2:db:f3:0c -56 6 Y -- NONE",
50
+ # "Chancery 0a:18:d6:0b:b9:c3 -82 11 Y -- NONE",
51
+ # "Chancery 2a:a4:3c:03:33:99 -59 60,+1 Y -- NONE",
52
+ # "DIRECT-sq-BRAVIA 02:71:cc:87:4a:8c -76 6 Y -- WPA2(PSK/AES/AES) ", #
44
53
  def available_network_info
45
54
  return nil unless wifi_on? # no need to try
46
55
  command = "#{AIRPORT_CMD} -s"
@@ -61,6 +70,7 @@ class MacOsModel < BaseModel
61
70
  # Reformat the line so that the name is left instead of right justified
62
71
  reformat_line.(line)
63
72
  end
73
+ # TODO: Need to sort case insensitively?:
64
74
  data_lines.sort!
65
75
  [reformat_line.(header_line)] + data_lines
66
76
  end
@@ -145,7 +155,7 @@ class MacOsModel < BaseModel
145
155
 
146
156
  # Returns data pertaining to "preferred" networks, many/most of which will probably not be available.
147
157
  def preferred_networks
148
- lines = run_os_command("networksetup -listpreferredwirelessnetworks #{wifi_hardware_port}").split("\n")
158
+ lines = run_os_command("networksetup -listpreferredwirelessnetworks #{wifi_port}").split("\n")
149
159
  # Produces something like this, unsorted, and with leading tabs:
150
160
  # Preferred networks on en0:
151
161
  # LibraryWiFi
@@ -158,6 +168,14 @@ class MacOsModel < BaseModel
158
168
  end
159
169
 
160
170
 
171
+ # Returns whether or not the specified interface is a WiFi interfae.
172
+ def is_wifi_port?(port)
173
+ run_os_command("networksetup -listpreferredwirelessnetworks #{port} 2>/dev/null")
174
+ exit_status = $?.exitstatus
175
+ exit_status != 10
176
+ end
177
+
178
+
161
179
  # Returns true if wifi is on, else false.
162
180
  def wifi_on?
163
181
  lines = run_os_command("#{AIRPORT_CMD} -I").split("\n")
@@ -168,7 +186,7 @@ class MacOsModel < BaseModel
168
186
  # Turns wifi on.
169
187
  def wifi_on
170
188
  return if wifi_on?
171
- run_os_command("networksetup -setairportpower #{wifi_hardware_port} on")
189
+ run_os_command("networksetup -setairportpower #{wifi_port} on")
172
190
  wifi_on? ? nil : raise("Wifi could not be enabled.")
173
191
  end
174
192
 
@@ -176,14 +194,14 @@ class MacOsModel < BaseModel
176
194
  # Turns wifi off.
177
195
  def wifi_off
178
196
  return unless wifi_on?
179
- run_os_command("networksetup -setairportpower #{wifi_hardware_port} off")
197
+ run_os_command("networksetup -setairportpower #{wifi_port} off")
180
198
  wifi_on? ? raise("Wifi could not be disabled.") : nil
181
199
  end
182
200
 
183
201
 
184
202
  # This method is called by BaseModel#connect to do the OS-specific connection logic.
185
203
  def os_level_connect(network_name, password = nil)
186
- command = "networksetup -setairportnetwork #{wifi_hardware_port} " + "#{Shellwords.shellescape(network_name)}"
204
+ command = "networksetup -setairportnetwork #{wifi_port} " + "#{Shellwords.shellescape(network_name)}"
187
205
  if password
188
206
  command << ' ' << Shellwords.shellescape(password)
189
207
  end
@@ -214,7 +232,7 @@ class MacOsModel < BaseModel
214
232
  # Returns the IP address assigned to the wifi port, or nil if none.
215
233
  def ip_address
216
234
  begin
217
- run_os_command("ipconfig getifaddr #{wifi_hardware_port}").chomp
235
+ run_os_command("ipconfig getifaddr #{wifi_port}").chomp
218
236
  rescue OsCommandError => error
219
237
  if error.exitstatus == 1
220
238
  nil
@@ -228,7 +246,7 @@ class MacOsModel < BaseModel
228
246
  def remove_preferred_network(network_name)
229
247
  network_name = network_name.to_s
230
248
  run_os_command("sudo networksetup -removepreferredwirelessnetwork " +
231
- "#{wifi_hardware_port} #{Shellwords.shellescape(network_name)}")
249
+ "#{wifi_port} #{Shellwords.shellescape(network_name)}")
232
250
  end
233
251
 
234
252
 
@@ -250,10 +268,16 @@ class MacOsModel < BaseModel
250
268
  # Returns some useful wifi-related information.
251
269
  def wifi_info
252
270
 
271
+ connected = begin
272
+ connected_to_internet?
273
+ rescue
274
+ false
275
+ end
276
+
253
277
  info = {
254
- 'wifi_on' => wifi_on?,
255
- 'internet_on' => connected_to_internet?,
256
- 'port' => wifi_hardware_port,
278
+ 'wifi_on' => wifi_on?,
279
+ 'internet_on' => connected,
280
+ 'port' => wifi_port,
257
281
  'network' => connected_network_name,
258
282
  'ip_address' => ip_address,
259
283
  'nameservers' => nameservers_using_scutil,
@@ -264,7 +288,7 @@ class MacOsModel < BaseModel
264
288
  info.merge!(more_info)
265
289
  info.delete('AirPort') # will be here if off, but info is already in wifi_on key
266
290
 
267
- if info['wifi_on']
291
+ if info['internet_on']
268
292
  begin
269
293
  info['public_ip'] = public_ip_address_info
270
294
  rescue => e
@@ -39,11 +39,6 @@ class OperatingSystems
39
39
  end
40
40
 
41
41
 
42
- def supported_os_names
43
- supported_operating_systems.map(&:display_name)
44
- end
45
-
46
-
47
42
  def current_id; current_os&.id; end
48
43
  def current_display_name; current_os&.display_name; end
49
44
 
@@ -17,7 +17,7 @@ class BaseOs
17
17
  raise MethodNotImplementedError.new
18
18
  end
19
19
 
20
- def create_model(want_verbose)
20
+ def create_model(options)
21
21
  raise MethodNotImplementedError.new
22
22
  end
23
23
 
@@ -12,7 +12,7 @@ class ImaginaryOs < BaseOs
12
12
  false
13
13
  end
14
14
 
15
- def create_model(want_verbose)
15
+ def create_model(options)
16
16
  raise "I was only kidding. This class is imaginary."
17
17
  end
18
18
  end
@@ -12,9 +12,9 @@ class MacOs < BaseOs
12
12
  !! /darwin/.match(RbConfig::CONFIG["host_os"])
13
13
  end
14
14
 
15
- def create_model(want_verbose)
15
+ def create_model(options)
16
16
  require_relative '../models/mac_os_model'
17
- MacOsModel.new(want_verbose)
17
+ MacOsModel.new(options)
18
18
  end
19
19
  end
20
20
  end
@@ -1,5 +1,5 @@
1
1
  module WifiWand
2
2
 
3
- VERSION = '2.4.2'
3
+ VERSION = '2.5.0'
4
4
 
5
5
  end
@@ -53,7 +53,7 @@ describe MacOsModel do
53
53
 
54
54
  it 'can get wifi port' do
55
55
  wifi_starts_on ? subject.wifi_on : subject.wifi_off
56
- subject.wifi_hardware_port
56
+ subject.wifi_port
57
57
  end
58
58
 
59
59
  it 'can list info' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wifi-wand
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.2
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keith Bennett
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-02-05 00:00:00.000000000 Z
11
+ date: 2018-03-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -101,7 +101,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
101
101
  version: '0'
102
102
  requirements: []
103
103
  rubyforge_project:
104
- rubygems_version: 2.7.3
104
+ rubygems_version: 2.7.6
105
105
  signing_key:
106
106
  specification_version: 4
107
107
  summary: Mac wifi utility