wifi-wand 2.4.2 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
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