mac-wifi 0.0.1
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 +7 -0
- data/.gitignore +19 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +261 -0
- data/bin/mac-wifi +771 -0
- data/mac-wifi.gemspec +24 -0
- data/spec/mac-wifi_spec.rb +114 -0
- metadata +53 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5763b7c0bc11d558c50e82e2e7fd52ba6bd5e3cb
|
4
|
+
data.tar.gz: 29878b297569049bff109647d9583ad6afad5a65
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5c70472f1d31785cae9a124ef5641f1157eb63549267c97773f120e4ce033588faad198bfded7f56ddb8e2eb8fbbeaae0d1ed31e20ddceaf5d0c7bd00140f8a6
|
7
|
+
data.tar.gz: 32007be9ff30e8fd38996ba433386a29ee11193c868d1b37afb1bc534e11c93173c9658a116bbc66d95692a26809b8864d7a78f1e8f1d28426e3c393ba223e9b
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2017 Keith Bennett
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,261 @@
|
|
1
|
+
# mac-wifi
|
2
|
+
|
3
|
+
This script enables the query and management of wifi configuration and environment on a Mac.
|
4
|
+
|
5
|
+
It can be run in single-command or interactive mode. Interactive mode uses the `pry` gem,
|
6
|
+
providing an interface familiar to Rubyists and other
|
7
|
+
[REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) users.
|
8
|
+
|
9
|
+
It is not necessary to download this repo; this script file is all you need to run the application.
|
10
|
+
|
11
|
+
### Usage
|
12
|
+
|
13
|
+
Available commands can be seen by using the `h` (or `help`) option. Here is its
|
14
|
+
output at the time of this writing:
|
15
|
+
|
16
|
+
```
|
17
|
+
➜ mac-wifi git:(master) ✗ ./mac-wifi h
|
18
|
+
|
19
|
+
Available commands are:
|
20
|
+
|
21
|
+
ci - connected to Internet (not just wifi on)?
|
22
|
+
co[nnect] network-name - turns wifi on, connects to network-name
|
23
|
+
cy[cle] - turns wifi off, then on, preserving network selection
|
24
|
+
d[isconnect] - disconnects from current network, does not turn off wifi
|
25
|
+
h[elp] - prints this help
|
26
|
+
i[nfo] - prints wifi-related information
|
27
|
+
lsp[referred] - lists preferred (not necessarily available) networks
|
28
|
+
lsa[vailable] - lists available networks
|
29
|
+
n[etwork_name] - name (SSID) of currently connected network
|
30
|
+
on - turns wifi on
|
31
|
+
of[f] - turns wifi off
|
32
|
+
pa[ssword] network-name - shows password for preferred network-name
|
33
|
+
q[uit] - exits this program (interactive shell mode only)
|
34
|
+
r[m] network-name - removes network-name from the preferred networks list
|
35
|
+
s[hell] - opens an interactive pry shell (command line only)
|
36
|
+
t[ill] - (experimental!) returns when the desired Internet connection state is true. Options:
|
37
|
+
'on'/:on or 'off'/:off
|
38
|
+
wait interval, in seconds (optional, defaults to 0.5 seconds)
|
39
|
+
w[ifion] - is the wifi on?
|
40
|
+
x[it] - exits this program (interactive shell mode only)
|
41
|
+
|
42
|
+
When in interactive shell mode:
|
43
|
+
* use quotes for string parameters such as method names.
|
44
|
+
* for pry commands, use prefix `%`.
|
45
|
+
```
|
46
|
+
|
47
|
+
Internally, it uses several Mac command line utilities. This is not ideal,
|
48
|
+
I would have preferred OS system calls, but the current approach enabled me to develop
|
49
|
+
this script quickly and simply.
|
50
|
+
|
51
|
+
### Pretty Output
|
52
|
+
|
53
|
+
For nicely formatted output of the `info` command in non-interactive mode,
|
54
|
+
the `awesome_print` gem is used if it is installed;
|
55
|
+
otherwise, the somewhat less awesome pretty print (`pp`) is used. Therefore,
|
56
|
+
installation of the `awesome_print` gem is recommended.
|
57
|
+
This is accomplished by the following command:
|
58
|
+
|
59
|
+
`gem install awesome_print`
|
60
|
+
|
61
|
+
|
62
|
+
### Seeing the Underlying OS Commands and Output
|
63
|
+
|
64
|
+
If you would like to see the Mac OS commands and their output, you can do so by setting the
|
65
|
+
environment variable MAC_WIFI_OPTS to include `-v` (for _verbose_).
|
66
|
+
This can be done in the following ways:
|
67
|
+
|
68
|
+
```
|
69
|
+
export MAC_WIFI_OPTS=-v
|
70
|
+
./mac-wifi i
|
71
|
+
```
|
72
|
+
|
73
|
+
```
|
74
|
+
MAC_WIFI_OPTS=-v ./mac-wifi i
|
75
|
+
```
|
76
|
+
|
77
|
+
You may notice that some commands are executed more than once. This is to simplify the application logic
|
78
|
+
and eliminate the need for the complexity of balancing the speed that a cache offers and the risk
|
79
|
+
of stale data.
|
80
|
+
|
81
|
+
|
82
|
+
### Troubleshooting
|
83
|
+
|
84
|
+
If you try to run the shell, the script will require the `pry` gem, so that will need to be installed.
|
85
|
+
`pry` in turn requires access to a `readline` library. If you encounter an error relating to finding a
|
86
|
+
`readline` library, this can be fixed by installing the `pry-coolline` gem: `gem install pry-coolline`.
|
87
|
+
If you are using the Ruby packaged with Mac OS, or for some other reason require root access to install
|
88
|
+
gems, you will need to precede those commands with `sudo`:
|
89
|
+
|
90
|
+
```
|
91
|
+
sudo gem install pry
|
92
|
+
sudo gem install pry-coolline
|
93
|
+
```
|
94
|
+
|
95
|
+
|
96
|
+
### Using the Shell
|
97
|
+
|
98
|
+
The shell, invoked with the `s` command on the command line, provides an interactive
|
99
|
+
session. It can be useful when:
|
100
|
+
|
101
|
+
* you want to issue multiple commands
|
102
|
+
* you want to combine commands
|
103
|
+
* you want the data in a format not provided by this application
|
104
|
+
* you want to incorporate these commands into other Ruby code interactively
|
105
|
+
* you want to combine the results of commands with other OS commands
|
106
|
+
(you can shell out to run other command line programs by preceding the command with a period (`.`).
|
107
|
+
|
108
|
+
### Using Variables in the Shell
|
109
|
+
|
110
|
+
There are a couple of things (that may be surprising) to keep in mind
|
111
|
+
when using the shell. They relate to the fact that local variables
|
112
|
+
and method calls use the same notation in Ruby (use of parentheses
|
113
|
+
in a method call is optional):
|
114
|
+
|
115
|
+
1) In Ruby, when both a method and a local variable have the same name,
|
116
|
+
the local variable will override the method name. Therefore, local variables
|
117
|
+
may override this app's commands. For example:
|
118
|
+
|
119
|
+
```
|
120
|
+
[1] pry(#<MacWifiView>)> n # network_name command
|
121
|
+
=> ".@ AIS SUPER WiFi"
|
122
|
+
[2] pry(#<MacWifiView>)> n = 123 # override it with a local variable
|
123
|
+
=> 123
|
124
|
+
[3] pry(#<MacWifiView>)> n # 'n' no longer calls the method
|
125
|
+
=> 123
|
126
|
+
[4] pry(#<MacWifiView>)> ne # but any other name starting with 'ne' will still call the method
|
127
|
+
=> ".@ AIS SUPER WiFi"
|
128
|
+
[5] pry(#<MacWifiView>)> network_name
|
129
|
+
=> ".@ AIS SUPER WiFi"
|
130
|
+
[6] pry(#<MacWifiView>)> ne_xzy123
|
131
|
+
=> ".@ AIS SUPER WiFi"
|
132
|
+
```
|
133
|
+
|
134
|
+
If you don't want to deal with this, you could use global variables, instance variables,
|
135
|
+
or constants, which will _not_ hide the methods:
|
136
|
+
|
137
|
+
```
|
138
|
+
[7] pry(#<MacWifiView>)> N = 123
|
139
|
+
[8] pry(#<MacWifiView>)> @n = 456
|
140
|
+
[9] pry(#<MacWifiView>)> $n = 789
|
141
|
+
[10] pry(#<MacWifiView>)> puts n, N, @n, $n
|
142
|
+
.@ AIS SUPER WiFi
|
143
|
+
123
|
144
|
+
456
|
145
|
+
789
|
146
|
+
=> nil
|
147
|
+
```
|
148
|
+
|
149
|
+
2) If you accidentally refer to a nonexistent variable or method name,
|
150
|
+
the result may be mysterious. For example, if I were write the wifi information
|
151
|
+
to a file, this would work:
|
152
|
+
|
153
|
+
|
154
|
+
```
|
155
|
+
[1] pry(#<MacWifiView>)> File.write('x.txt', info)
|
156
|
+
=> 431
|
157
|
+
```
|
158
|
+
|
159
|
+
However, if I forget to quote the filename:
|
160
|
+
|
161
|
+
```
|
162
|
+
[2] pry(#<MacWifiView>)> File.write(x.txt, info)
|
163
|
+
➜ mac-wifi git:(master) ✗
|
164
|
+
```
|
165
|
+
|
166
|
+
What happened? `x.txt` was assumed by Ruby to be a method name.
|
167
|
+
`method_missing` was called, and since `x.txt` starts with `x`,
|
168
|
+
the exit method was called, exiting the program.
|
169
|
+
|
170
|
+
Bottom line is, be careful to quote your strings, and you're probably better off using
|
171
|
+
constants or instance variables if you want to create variables in your shell.
|
172
|
+
|
173
|
+
|
174
|
+
|
175
|
+
### Examples
|
176
|
+
|
177
|
+
#### Single Command Invocations
|
178
|
+
|
179
|
+
```
|
180
|
+
mac-wifi i # prints out wifi info
|
181
|
+
mac-wifi lsa # prints available networks
|
182
|
+
mac-wifi lsp # prints preferred networks
|
183
|
+
mac-wifi cy # cycles the wifi off and on
|
184
|
+
mac-wifi co a-network a-password # connects to a network requiring a password
|
185
|
+
mac-wifi co a-network # connects to a network _not_ requiring a password
|
186
|
+
mac-wifi t on && say "Internet connected" # Play audible message when Internet becomes connected
|
187
|
+
```
|
188
|
+
|
189
|
+
#### Interactive Shell Commands
|
190
|
+
|
191
|
+
(For brevity, semicolons are used here to put multiple commands on one line,
|
192
|
+
but these commands could also each be specified on a line of its own.)
|
193
|
+
|
194
|
+
```
|
195
|
+
# Print out wifi info:
|
196
|
+
i
|
197
|
+
|
198
|
+
# Cycle (off/on) the network then connect to the specified network not requiring a password
|
199
|
+
> cycle; connect 'my-network'
|
200
|
+
|
201
|
+
# Cycle (off/on) the network, then connect to the same network not requiring a password
|
202
|
+
> @name = network_name; cycle; connect @name
|
203
|
+
|
204
|
+
# Cycle (off/on) the network then connect to the specified network using the specified password
|
205
|
+
> cycle; connect 'my-network', 'my-password'
|
206
|
+
|
207
|
+
> @i = i; puts "You are connected on port #{@i[:port]} to #{@i[:network]} on IP address #{@i[:ip_address]}."
|
208
|
+
You are connected on port en0 to .@ AIS SUPER WiFi on IP address 172.27.145.225.
|
209
|
+
|
210
|
+
> puts "There are #{lsp.size} preferred networks."
|
211
|
+
There are 341 preferred networks.
|
212
|
+
|
213
|
+
# Delete all preferred networks whose names begin with "TOTTGUEST":
|
214
|
+
> lsp.grep(/^TOTTGUEST/).each { |n| puts "Deleting preferred network #{n}."; rm(n) }
|
215
|
+
|
216
|
+
# Define a method to wait for the Internet connection to be active.
|
217
|
+
# (This functionality is included in the `till` command.)
|
218
|
+
# Call it, then output celebration message:
|
219
|
+
[17] pry(#<MacWifiView>)> def wait_for_internet; loop do; break if ci; sleep 0.5; end; end
|
220
|
+
[18] pry(#<MacWifiView>)> wait_for_internet; puts "Connected!"
|
221
|
+
Connected!
|
222
|
+
|
223
|
+
# Same, but using a lambda instead of a method so we can use a variable name
|
224
|
+
# and not need to worry about method name collision:
|
225
|
+
@wait_for_internet = -> { loop do; break if ci; sleep 0.5; end }
|
226
|
+
@wait_for_internet.() ; puts "Connected!"
|
227
|
+
Connected!
|
228
|
+
```
|
229
|
+
|
230
|
+
### Cautions
|
231
|
+
|
232
|
+
If the wifi networking changes from on to off while `till(:off)` is waiting,
|
233
|
+
the program will hang indefinitely. I am currently looking into this problem.
|
234
|
+
It also happens outside of this program; you can reproduce it by running `curl`,
|
235
|
+
preferably on a longer running request, then turning off your wifi. This is pretty
|
236
|
+
serious, and I'm considering removing the `:off` option from the `till` command.
|
237
|
+
|
238
|
+
|
239
|
+
|
240
|
+
### Password Lookup Oddity
|
241
|
+
|
242
|
+
You may find it odd (I did, anyway) that even if you issue the password command
|
243
|
+
(`mac_wifi pa a-network-name`) using sudo, you will still be prompted
|
244
|
+
with a graphical dialog for both a user id and password. This is no doubt
|
245
|
+
for better security, but it's unfortunate in that it makes it impossible to fully automate this task.
|
246
|
+
|
247
|
+
In particular, it would be nice for the `cycle` command to be able to reconnect to the original
|
248
|
+
network after turning the network on. This is not possible where that network required a password.
|
249
|
+
If you don't mind storing the network password in plain text somewhere, then you could easily
|
250
|
+
automate it (e.g. `mac-wifi cycle && mac-wifi connect a-network a-password`).
|
251
|
+
|
252
|
+
|
253
|
+
### License
|
254
|
+
|
255
|
+
MIT License (see LICENSE.txt)
|
256
|
+
|
257
|
+
### Shameless Ad
|
258
|
+
|
259
|
+
I am available for consulting, development, tutoring, training, troubleshooting, etc.
|
260
|
+
|
261
|
+
You can contact me via GMail, Twitter, and Github as _keithrbennett_.
|
data/bin/mac-wifi
ADDED
@@ -0,0 +1,771 @@
|
|
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
|
+
|
23
|
+
require 'shellwords'
|
24
|
+
require 'tempfile'
|
25
|
+
|
26
|
+
module MacWifi
|
27
|
+
|
28
|
+
# This version must be kept in sync with the version in the gemspec file.
|
29
|
+
VERSION = '0.0.1'
|
30
|
+
|
31
|
+
class Model
|
32
|
+
|
33
|
+
AIRPORT_CMD = '/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport'
|
34
|
+
|
35
|
+
|
36
|
+
def initialize(verbose = false)
|
37
|
+
@verbose_mode = verbose
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
def connected_to_internet_curl_unused?
|
42
|
+
|
43
|
+
connected = false
|
44
|
+
|
45
|
+
begin
|
46
|
+
run_os_command('curl --silent --head http://www.google.com/')
|
47
|
+
connected = true
|
48
|
+
rescue OsCommandError => e
|
49
|
+
if e.exitstatus == 6
|
50
|
+
connected = false
|
51
|
+
else
|
52
|
+
raise
|
53
|
+
end
|
54
|
+
end
|
55
|
+
connected
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
# This method returns whether or not there is a working Internet connection.
|
60
|
+
# Because of a Mac issue which causes a request to hang if the network is turned
|
61
|
+
# off during its lifetime, we give it only 5 seconds per try,
|
62
|
+
# and limit the number of tries to 3.
|
63
|
+
def connected_to_internet?
|
64
|
+
|
65
|
+
tempfile = Tempfile.open('mac-wifi-')
|
66
|
+
|
67
|
+
begin
|
68
|
+
start_status_script = -> do
|
69
|
+
script = "curl --silent --head http://www.google.com/ > /dev/null ; echo $? > #{tempfile.path} &"
|
70
|
+
pid = Process.spawn(script)
|
71
|
+
Process.detach(pid)
|
72
|
+
pid
|
73
|
+
end
|
74
|
+
|
75
|
+
process_is_running = ->(pid) do
|
76
|
+
script = %Q{ps -p #{pid} > /dev/null; echo $?}
|
77
|
+
output = `#{script}`.chomp
|
78
|
+
output == "0"
|
79
|
+
end
|
80
|
+
|
81
|
+
get_connected_state_from_curl = -> do
|
82
|
+
tempfile.close
|
83
|
+
File.read(tempfile.path).chomp == '0'
|
84
|
+
end
|
85
|
+
|
86
|
+
# Do one run, iterating during the timeout period to see if the command has completed
|
87
|
+
do_one_run = -> do
|
88
|
+
end_time = Time.now + 3
|
89
|
+
pid = start_status_script.()
|
90
|
+
while Time.now < end_time
|
91
|
+
if process_is_running.(pid)
|
92
|
+
sleep 0.5
|
93
|
+
else
|
94
|
+
return get_connected_state_from_curl.()
|
95
|
+
end
|
96
|
+
end
|
97
|
+
Process.kill('KILL', pid)
|
98
|
+
:hung
|
99
|
+
end
|
100
|
+
|
101
|
+
3.times do
|
102
|
+
connected = do_one_run.()
|
103
|
+
return connected if connected != :hung
|
104
|
+
end
|
105
|
+
|
106
|
+
raise "Could not determine Internet status."
|
107
|
+
|
108
|
+
ensure
|
109
|
+
tempfile.unlink
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
# This implementation often hangs when wifi is turned off while curl is active
|
115
|
+
# def connected_to_internet?
|
116
|
+
# script = "curl --silent --head http://www.google.com/ > /dev/null ; echo $?"
|
117
|
+
# result = `#{script}`.chomp
|
118
|
+
# puts result
|
119
|
+
# result == '0'
|
120
|
+
# end
|
121
|
+
|
122
|
+
|
123
|
+
# This is determined by whether or not a line like the following appears in the output of `netstat -nr`:
|
124
|
+
# 0/1 10.137.0.41 UGSc 15 0 utun1
|
125
|
+
def vpn_running?
|
126
|
+
run_os_command('netstat -nr').split("\n").grep(/^0\/1.*utun1/).any?
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
# Identifies the (first) wireless network hardware port in the system, e.g. en0 or en1
|
131
|
+
def wifi_hardware_port
|
132
|
+
@wifi_hardware_port ||= begin
|
133
|
+
lines = run_os_command("networksetup -listallhardwareports").split("\n")
|
134
|
+
# Produces something like this:
|
135
|
+
# Hardware Port: Wi-Fi
|
136
|
+
# Device: en0
|
137
|
+
# Ethernet Address: ac:bc:32:b9:a9:9d
|
138
|
+
#
|
139
|
+
# Hardware Port: Bluetooth PAN
|
140
|
+
# Device: en3
|
141
|
+
# Ethernet Address: ac:bc:32:b9:a9:9e
|
142
|
+
wifi_port_line_num = (0...lines.size).detect do |index|
|
143
|
+
/: Wi-Fi$/.match(lines[index])
|
144
|
+
end
|
145
|
+
if wifi_port_line_num.nil?
|
146
|
+
raise %Q{Wifi port (e.g. "en0") not found in output of: networksetup -listallhardwareports}
|
147
|
+
else
|
148
|
+
lines[wifi_port_line_num + 1].split(': ').last
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
# Returns data pertaining to available wireless networks.
|
155
|
+
# For some reason, this often returns no results, so I've put the operation in a loop.
|
156
|
+
def available_network_info
|
157
|
+
return nil unless wifi_on? # no need to try
|
158
|
+
command = "#{AIRPORT_CMD} -s"
|
159
|
+
max_attempts = 50
|
160
|
+
max_attempts.times do
|
161
|
+
output = run_os_command(command)
|
162
|
+
if output.size > 0
|
163
|
+
return output.split("\n")
|
164
|
+
end
|
165
|
+
end
|
166
|
+
raise "Unable to get available network information after #{max_attempts} attempts."
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
# Returns data pertaining to "preferred" networks, many/most of which will probably not be available.
|
171
|
+
def preferred_networks
|
172
|
+
lines = run_os_command("networksetup -listpreferredwirelessnetworks #{wifi_hardware_port}").split("\n")
|
173
|
+
# Produces something like this, unsorted, and with leading tabs:
|
174
|
+
# Preferred networks on en0:
|
175
|
+
# LibraryWiFi
|
176
|
+
# @thePAD/Magma
|
177
|
+
|
178
|
+
lines.delete_at(0) # remove title line
|
179
|
+
lines.map! { |line| line.gsub("\t", '') } # remove leading tabs
|
180
|
+
lines.sort! { |s1, s2| s1.casecmp(s2) } # sort alphabetically, case insensitively
|
181
|
+
lines
|
182
|
+
end
|
183
|
+
|
184
|
+
|
185
|
+
# Returns true if wifi is on, else false.
|
186
|
+
def wifi_on?
|
187
|
+
lines = run_os_command("#{AIRPORT_CMD} -I").split("\n")
|
188
|
+
! lines.grep("AirPort: Off").any?
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
# Turns wifi on.
|
193
|
+
def wifi_on
|
194
|
+
return if wifi_on?
|
195
|
+
run_os_command("networksetup -setairportpower #{wifi_hardware_port} on")
|
196
|
+
wifi_on? ? nil : raise("Wifi could not be enabled.")
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
# Turns wifi off.
|
201
|
+
def wifi_off
|
202
|
+
return unless wifi_on?
|
203
|
+
run_os_command("networksetup -setairportpower #{wifi_hardware_port} off")
|
204
|
+
wifi_on? ? raise("Wifi could not be disabled.") : nil
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
# Turns wifi off and then on, reconnecting to the originally connecting network.
|
209
|
+
def cycle_network
|
210
|
+
# TODO: Make this network name saving and restoring conditional on it not having a password.
|
211
|
+
# If the disabled code below is enabled, an error will be raised if a password is required,
|
212
|
+
# even though it is stored.
|
213
|
+
# network_name = current_network
|
214
|
+
wifi_off
|
215
|
+
wifi_on
|
216
|
+
# connect(network_name) if network_name
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
def connected_network_name
|
221
|
+
wifi_info['SSID']
|
222
|
+
end
|
223
|
+
|
224
|
+
|
225
|
+
def connected_to?(network_name)
|
226
|
+
network_name == connected_network_name
|
227
|
+
end
|
228
|
+
|
229
|
+
|
230
|
+
# Connects to the passed network name, optionally with password.
|
231
|
+
# Turns wifi on first, in case it was turned off.
|
232
|
+
def connect(network_name, password = nil)
|
233
|
+
if network_name.nil? || network_name.empty?
|
234
|
+
raise "A network name is required but was not provided."
|
235
|
+
end
|
236
|
+
wifi_on
|
237
|
+
command = "networksetup -setairportnetwork #{wifi_hardware_port} " + "#{Shellwords.shellescape(network_name)}"
|
238
|
+
if password
|
239
|
+
command << ' ' << Shellwords.shellescape(password)
|
240
|
+
end
|
241
|
+
run_os_command(command)
|
242
|
+
|
243
|
+
# Verify that the network is now connected:
|
244
|
+
actual_network_name = connected_network_name
|
245
|
+
unless actual_network_name == network_name
|
246
|
+
message = %Q{Expected to connect to "#{network_name}" but }
|
247
|
+
if actual_network_name
|
248
|
+
message << %Q{connected to "#{connected_network_name}" instead.}
|
249
|
+
else
|
250
|
+
message << "unable to connect to any network. Did you "
|
251
|
+
end
|
252
|
+
message << (password ? "provide the correct password?" : "need to provide a password?")
|
253
|
+
raise message
|
254
|
+
end
|
255
|
+
nil
|
256
|
+
end
|
257
|
+
|
258
|
+
|
259
|
+
# @return:
|
260
|
+
# If the network is in the preferred networks list
|
261
|
+
# If a password is associated w/this network, return the password
|
262
|
+
# If not, return nil
|
263
|
+
# else
|
264
|
+
# raise an error
|
265
|
+
def preferred_network_password(preferred_network_name)
|
266
|
+
if preferred_networks.include?(preferred_network_name)
|
267
|
+
command = %Q{security find-generic-password -D "AirPort network password" -a "#{preferred_network_name}" -w 2>&1}
|
268
|
+
begin
|
269
|
+
return run_os_command(command).chomp
|
270
|
+
rescue OsCommandError => error
|
271
|
+
if error.exitstatus == 44 # network has no password stored
|
272
|
+
nil
|
273
|
+
else
|
274
|
+
raise
|
275
|
+
end
|
276
|
+
end
|
277
|
+
else
|
278
|
+
raise "Network #{preferred_network_name} not in preferred networks list."
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
|
283
|
+
# Returns the IP address assigned to the wifi port, or nil if none.
|
284
|
+
def ip_address
|
285
|
+
begin
|
286
|
+
run_os_command("ipconfig getifaddr #{wifi_hardware_port}").chomp
|
287
|
+
rescue OsCommandError => error
|
288
|
+
if error.exitstatus == 1
|
289
|
+
nil
|
290
|
+
else
|
291
|
+
raise
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
|
297
|
+
# Removes the specified network(s) from the preferred network list.
|
298
|
+
# @param network_names names of networks to remove; may be empty or contain nonexistent networks
|
299
|
+
# @return names of the networks that were removed (excludes non-preexisting networks)
|
300
|
+
def remove_preferred_networks(*network_names)
|
301
|
+
networks_to_remove = network_names & preferred_networks # exclude any nonexistent networks
|
302
|
+
networks_to_remove.each do |name|
|
303
|
+
run_os_command("sudo networksetup -removepreferredwirelessnetwork " +
|
304
|
+
"#{wifi_hardware_port} #{Shellwords.shellescape(name)}")
|
305
|
+
end
|
306
|
+
networks_to_remove
|
307
|
+
end
|
308
|
+
|
309
|
+
|
310
|
+
# Returns the network currently connected to, or nil if none.
|
311
|
+
def current_network
|
312
|
+
lines = run_os_command("#{AIRPORT_CMD} -I").split("\n")
|
313
|
+
ssid_lines = lines.grep(/ SSID:/)
|
314
|
+
ssid_lines.empty? ? nil : ssid_lines.first.split('SSID: ').last.strip
|
315
|
+
end
|
316
|
+
|
317
|
+
|
318
|
+
# Disconnects from the currently connected network. Does not turn off wifi.
|
319
|
+
def disconnect
|
320
|
+
run_os_command("sudo #{AIRPORT_CMD} -z")
|
321
|
+
nil
|
322
|
+
end
|
323
|
+
|
324
|
+
|
325
|
+
# Returns some useful wifi-related information.
|
326
|
+
def wifi_info
|
327
|
+
|
328
|
+
info = {
|
329
|
+
wifi_on: wifi_on?,
|
330
|
+
internet_on: connected_to_internet?,
|
331
|
+
vpn_on: vpn_running?,
|
332
|
+
port: wifi_hardware_port,
|
333
|
+
network: current_network,
|
334
|
+
ip_address: ip_address,
|
335
|
+
timestamp: Time.now,
|
336
|
+
}
|
337
|
+
more_output = run_os_command(AIRPORT_CMD + " -I")
|
338
|
+
more_info = colon_output_to_hash(more_output)
|
339
|
+
info.merge!(more_info)
|
340
|
+
info.delete('AirPort') # will be here if off, but info is already in wifi_on key
|
341
|
+
info
|
342
|
+
end
|
343
|
+
|
344
|
+
|
345
|
+
# Waits for the Internet connection to be in the desired state.
|
346
|
+
# @param status must be in [:conn, :disc, :off, :on]; waits for that state
|
347
|
+
# @param wait_interval_in_secs sleeps this interval between retries
|
348
|
+
#
|
349
|
+
# NOTE!: This is experimental and sometimes hangs.
|
350
|
+
def till(status, wait_interval_in_secs = nil)
|
351
|
+
|
352
|
+
wait_interval_in_secs ||= 0.5
|
353
|
+
|
354
|
+
exit_when = case status
|
355
|
+
when :conn
|
356
|
+
-> { connected_to_internet? }
|
357
|
+
when :disc
|
358
|
+
-> { ! connected_to_internet? }
|
359
|
+
when :on
|
360
|
+
-> { wifi_on? }
|
361
|
+
when :off
|
362
|
+
-> { ! wifi_on? }
|
363
|
+
else
|
364
|
+
raise ArgumentError.new("Option must be one of [:conn, :disc, :off, :on]. Was: #{status.inspect}")
|
365
|
+
end
|
366
|
+
|
367
|
+
loop do
|
368
|
+
return if exit_when.()
|
369
|
+
sleep(wait_interval_in_secs)
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
|
374
|
+
class OsCommandError < RuntimeError
|
375
|
+
attr_reader :exitstatus, :command, :text
|
376
|
+
|
377
|
+
def initialize(exitstatus, command, text)
|
378
|
+
@exitstatus = exitstatus
|
379
|
+
@command = command
|
380
|
+
@text = text
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
|
385
|
+
def run_os_command(command)
|
386
|
+
output = `#{command} 2>&1` # join stderr with stdout
|
387
|
+
if $?.exitstatus != 0
|
388
|
+
raise OsCommandError.new($?.exitstatus, command, output)
|
389
|
+
end
|
390
|
+
if @verbose_mode
|
391
|
+
puts "\n\n#{'-' * 79}\nCommand: #{command}\n\nOutput:\n#{output}#{'-' * 79}\n\n"
|
392
|
+
end
|
393
|
+
output
|
394
|
+
end
|
395
|
+
private :run_os_command
|
396
|
+
|
397
|
+
|
398
|
+
# Parses output like the text below into a hash:
|
399
|
+
# SSID: Pattara211
|
400
|
+
# MCS: 5
|
401
|
+
# channel: 7
|
402
|
+
def colon_output_to_hash(output)
|
403
|
+
lines = output.split("\n")
|
404
|
+
lines.each_with_object({}) do |line, new_hash|
|
405
|
+
key, value = line.split(': ')
|
406
|
+
key = key.strip
|
407
|
+
value.strip! if value
|
408
|
+
new_hash[key] = value
|
409
|
+
end
|
410
|
+
end
|
411
|
+
private :colon_output_to_hash
|
412
|
+
end
|
413
|
+
|
414
|
+
|
415
|
+
class CommandLineInterface
|
416
|
+
|
417
|
+
attr_reader :model
|
418
|
+
|
419
|
+
# Help text to be used when requested by 'h' command, in case of unrecognized or nonexistent command, etc.
|
420
|
+
HELP_TEXT = "
|
421
|
+
mac-wifi version #{VERSION} -- Available commands are:
|
422
|
+
|
423
|
+
ci - connected to Internet (not just wifi on)?
|
424
|
+
co[nnect] network-name - turns wifi on, connects to network-name
|
425
|
+
cy[cle] - turns wifi off, then on, preserving network selection
|
426
|
+
d[isconnect] - disconnects from current network, does not turn off wifi
|
427
|
+
h[elp] - prints this help
|
428
|
+
i[nfo] - prints wifi-related information
|
429
|
+
lsp[referred] - lists preferred (not necessarily available) networks
|
430
|
+
lsa[vailable] - lists available networks
|
431
|
+
n[etwork_name] - name (SSID) of currently connected network
|
432
|
+
on - turns wifi on
|
433
|
+
of[f] - turns wifi off
|
434
|
+
pa[ssword] network-name - shows password for preferred network-name
|
435
|
+
q[uit] - exits this program (interactive shell mode only)
|
436
|
+
r[m] network-name - removes network-name from the preferred networks list
|
437
|
+
s[hell] - opens an interactive pry shell (command line only)
|
438
|
+
t[ill] - (experimental!) returns when the desired Internet connection state is true. Options:
|
439
|
+
'on'/:on or 'off'/:off
|
440
|
+
wait interval, in seconds (optional, defaults to 0.5 seconds)
|
441
|
+
w[ifion] - is the wifi on?
|
442
|
+
x[it] - exits this program (interactive shell mode only)
|
443
|
+
|
444
|
+
When in interactive shell mode:
|
445
|
+
* use quotes for string parameters such as method names.
|
446
|
+
* for pry commands, use prefix `%`.
|
447
|
+
|
448
|
+
"
|
449
|
+
|
450
|
+
|
451
|
+
def initialize
|
452
|
+
@model = Model.new(verbose_mode)
|
453
|
+
@interactive_mode = false # will be true if/when user selects shell option on command line
|
454
|
+
end
|
455
|
+
|
456
|
+
|
457
|
+
class Command < Struct.new(:regex, :action); end
|
458
|
+
|
459
|
+
|
460
|
+
class BadCommandError < RuntimeError
|
461
|
+
def initialize(error_message)
|
462
|
+
super
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
|
467
|
+
def verbose_mode
|
468
|
+
/-v/.match(ENV['MAC_WIFI_OPTS'])
|
469
|
+
end
|
470
|
+
|
471
|
+
|
472
|
+
def print_help
|
473
|
+
puts HELP_TEXT
|
474
|
+
end
|
475
|
+
|
476
|
+
|
477
|
+
# We'd like to use awesome_print if it is available, but not require it.
|
478
|
+
# So, we try to require it, but if that fails, we fall back to using pp (pretty print).
|
479
|
+
# Returns true if awesome_print is available (after requiring it), else false after requiring 'pp'.
|
480
|
+
def awesome_print_available?
|
481
|
+
if @awesome_print_available.nil? # first time here
|
482
|
+
begin
|
483
|
+
require 'awesome_print'
|
484
|
+
@awesome_print_available = true
|
485
|
+
rescue LoadError
|
486
|
+
require 'pp'
|
487
|
+
@awesome_print_available = false
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
@awesome_print_available
|
492
|
+
end
|
493
|
+
|
494
|
+
|
495
|
+
def fancy_puts(object)
|
496
|
+
awesome_print_available? ? ap(object) : pp(object)
|
497
|
+
end
|
498
|
+
|
499
|
+
|
500
|
+
# Asserts that a command has been passed on the command line.
|
501
|
+
def validate_command_line
|
502
|
+
if ARGV.empty?
|
503
|
+
puts "Syntax is: #{__FILE__} command [options]"
|
504
|
+
print_help
|
505
|
+
exit(-1)
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
509
|
+
|
510
|
+
# Pry will output the content of the method from which it was called.
|
511
|
+
# This small method exists solely to reduce the amount of pry's output
|
512
|
+
# that is not needed here.
|
513
|
+
def run_pry
|
514
|
+
binding.pry
|
515
|
+
end
|
516
|
+
|
517
|
+
|
518
|
+
# Runs a pry session in the context of this object.
|
519
|
+
# Commands and options specified on the command line can also be specified in the shell.
|
520
|
+
def run_shell
|
521
|
+
if @interactive_mode
|
522
|
+
puts "Already in shell."
|
523
|
+
return
|
524
|
+
end
|
525
|
+
|
526
|
+
@interactive_mode = true
|
527
|
+
|
528
|
+
begin
|
529
|
+
require 'pry'
|
530
|
+
rescue LoadError
|
531
|
+
puts "The 'pry' gem, required for running the shell, was not found. Please `gem install pry`."
|
532
|
+
exit(-1)
|
533
|
+
end
|
534
|
+
|
535
|
+
print_help
|
536
|
+
|
537
|
+
# Enable the line below if you have any problems with pry configuration being loaded
|
538
|
+
# that is messing up this runtime use of pry:
|
539
|
+
# Pry.config.should_load_rc = false
|
540
|
+
|
541
|
+
# Strangely, this is the only thing I have found that successfully suppresses the
|
542
|
+
# code context output, which is not useful here. Anyway, this will differentiate
|
543
|
+
# a pry command from a DSL command, which _is_ useful here.
|
544
|
+
Pry.config.command_prefix = '%'
|
545
|
+
|
546
|
+
run_pry
|
547
|
+
end
|
548
|
+
|
549
|
+
|
550
|
+
def output_network_password(network_name, password)
|
551
|
+
output = %Q{Preferred network "#{network_name}" }
|
552
|
+
output << (password ? %Q{stored password is: "#{password}".} : "has no stored password.")
|
553
|
+
puts output
|
554
|
+
password
|
555
|
+
end
|
556
|
+
|
557
|
+
|
558
|
+
# For use by the shell; when typing a command and options, it is passed to process_command_line
|
559
|
+
def method_missing(method_name, *options)
|
560
|
+
method_valid = !! find_command_action(method_name.to_s)
|
561
|
+
if method_valid
|
562
|
+
process_command_line(method_name, options)
|
563
|
+
else
|
564
|
+
puts(%Q{"#{method_name}" is not a valid command or option. If you intend for this to be a string literal, use quotes.})
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
|
569
|
+
# Processes the command (ARGV[0]) and any relevant options (ARGV[1..-1]).
|
570
|
+
#
|
571
|
+
# CAUTION! In interactive mode, any strings entered (e.g. a network name) MUST
|
572
|
+
# be in a form that Ruby will recognize as a string, i.e. single or double quotes,
|
573
|
+
# %q, %Q, etc. Otherwise Ruby will assume it's a method name and pass it to
|
574
|
+
# method_missing!
|
575
|
+
def process_command_line(command, options)
|
576
|
+
action = find_command_action(command)
|
577
|
+
if action
|
578
|
+
action.(*options)
|
579
|
+
else
|
580
|
+
print_help
|
581
|
+
raise BadCommandError.new(
|
582
|
+
"Unrecognized command. Command was #{action} and options were #{options.inspect}.")
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
|
587
|
+
def quit
|
588
|
+
if @interactive_mode
|
589
|
+
exit(0)
|
590
|
+
else
|
591
|
+
puts "This command can only be run in shell mode."
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
|
596
|
+
def cmd_ci
|
597
|
+
connected = model.connected_to_internet?
|
598
|
+
puts "Connected to Internet: #{connected}" unless @interactive_mode
|
599
|
+
connected
|
600
|
+
end
|
601
|
+
|
602
|
+
|
603
|
+
def cmd_co(network, password = nil)
|
604
|
+
model.connect(network, password)
|
605
|
+
end
|
606
|
+
|
607
|
+
|
608
|
+
def cmd_cy
|
609
|
+
model.cycle_network
|
610
|
+
end
|
611
|
+
|
612
|
+
|
613
|
+
def cmd_d
|
614
|
+
model.disconnect
|
615
|
+
end
|
616
|
+
|
617
|
+
|
618
|
+
def cmd_h
|
619
|
+
print_help
|
620
|
+
end
|
621
|
+
|
622
|
+
|
623
|
+
def cmd_i
|
624
|
+
info = model.wifi_info
|
625
|
+
fancy_puts(info) unless @interactive_mode
|
626
|
+
info
|
627
|
+
end
|
628
|
+
|
629
|
+
|
630
|
+
def cmd_lsa
|
631
|
+
info = model.available_network_info
|
632
|
+
puts info unless @interactive_mode
|
633
|
+
info
|
634
|
+
end
|
635
|
+
|
636
|
+
|
637
|
+
def cmd_lsp
|
638
|
+
networks = model.preferred_networks
|
639
|
+
puts networks unless @interactive_mode
|
640
|
+
networks
|
641
|
+
end
|
642
|
+
|
643
|
+
|
644
|
+
def cmd_n
|
645
|
+
name = model.connected_network_name
|
646
|
+
unless @interactive_mode
|
647
|
+
puts "Network (SSID) name: #{name ? name : '[none]'}"
|
648
|
+
end
|
649
|
+
name
|
650
|
+
end
|
651
|
+
|
652
|
+
|
653
|
+
def cmd_of
|
654
|
+
model.wifi_off
|
655
|
+
end
|
656
|
+
|
657
|
+
|
658
|
+
def cmd_on
|
659
|
+
model.wifi_on
|
660
|
+
end
|
661
|
+
|
662
|
+
|
663
|
+
def cmd_pa(network)
|
664
|
+
password = model.preferred_network_password(network)
|
665
|
+
output_network_password(network, password) unless @interactive_mode
|
666
|
+
password
|
667
|
+
end
|
668
|
+
|
669
|
+
|
670
|
+
def cmd_q
|
671
|
+
quit
|
672
|
+
end
|
673
|
+
|
674
|
+
|
675
|
+
def cmd_r(*options)
|
676
|
+
model.remove_preferred_networks(*options)
|
677
|
+
end
|
678
|
+
|
679
|
+
|
680
|
+
def cmd_s
|
681
|
+
run_shell
|
682
|
+
end
|
683
|
+
|
684
|
+
|
685
|
+
def cmd_t(*options)
|
686
|
+
target_status = options[0].to_sym
|
687
|
+
wait_interval_in_secs = (options[1] ? Float(options[1]) : nil)
|
688
|
+
model.till(target_status, wait_interval_in_secs)
|
689
|
+
end
|
690
|
+
|
691
|
+
|
692
|
+
def cmd_w
|
693
|
+
on = model.wifi_on?
|
694
|
+
puts "Wifi on?: #{on}" unless @interactive_mode
|
695
|
+
on
|
696
|
+
end
|
697
|
+
|
698
|
+
|
699
|
+
def cmd_x
|
700
|
+
quit
|
701
|
+
end
|
702
|
+
|
703
|
+
|
704
|
+
def commands
|
705
|
+
@commands_ ||= [
|
706
|
+
Command.new(/^ci/, -> (*_options) { cmd_ci }),
|
707
|
+
Command.new(/^co/, -> (*options) { cmd_co(*options) }),
|
708
|
+
Command.new(/^cy/, -> (*_options) { cmd_cy }),
|
709
|
+
Command.new(/^d/, -> (*_options) { cmd_d }),
|
710
|
+
Command.new(/^h/, -> (*_options) { cmd_h }),
|
711
|
+
Command.new(/^i/, -> (*_options) { cmd_i }),
|
712
|
+
Command.new(/^lsa/, -> (*_options) { cmd_lsa }),
|
713
|
+
Command.new(/^lsp/, -> (*_options) { cmd_lsp }),
|
714
|
+
Command.new(/^n/, -> (*_options) { cmd_n }),
|
715
|
+
Command.new(/^of/, -> (*_options) { cmd_of }),
|
716
|
+
Command.new(/^on/, -> (*_options) { cmd_on }),
|
717
|
+
Command.new(/^pa/, -> (*options) { cmd_pa(*options) }),
|
718
|
+
Command.new(/^q/, -> (*_options) { cmd_q }),
|
719
|
+
Command.new(/^r/, -> (*options) { cmd_r(*options) }),
|
720
|
+
Command.new(/^s/, -> (*_options) { cmd_s }),
|
721
|
+
Command.new(/^t/, -> (*options) { cmd_t(*options) }),
|
722
|
+
Command.new(/^w/, -> (*_options) { cmd_w }),
|
723
|
+
Command.new(/^x/, -> (*_options) { cmd_x })
|
724
|
+
]
|
725
|
+
end
|
726
|
+
|
727
|
+
|
728
|
+
def find_command_action(command_string)
|
729
|
+
result = commands.detect { |cmd| cmd.regex.match(command_string) }
|
730
|
+
result ? result.action : nil
|
731
|
+
end
|
732
|
+
|
733
|
+
|
734
|
+
def call
|
735
|
+
validate_command_line
|
736
|
+
begin
|
737
|
+
process_command_line(ARGV[0], ARGV[1..-1])
|
738
|
+
rescue BadCommandError => error
|
739
|
+
separator_line = "#{'!' * 79}\n"
|
740
|
+
puts separator_line + "Bad command: #{ARGV[0]}\n" + separator_line + "\n"
|
741
|
+
exit(-1)
|
742
|
+
end
|
743
|
+
end
|
744
|
+
|
745
|
+
end
|
746
|
+
|
747
|
+
end
|
748
|
+
|
749
|
+
|
750
|
+
# @return true if this file is being run as a script, else false
|
751
|
+
#
|
752
|
+
# This file could be called as a script in either of these two ways:
|
753
|
+
#
|
754
|
+
# 1) by loading this file directly, or
|
755
|
+
# 2) by running as a gem executable's binstub, in (relatively) '../../../bin'
|
756
|
+
|
757
|
+
def running_as_script?
|
758
|
+
return true if __FILE__ == $0
|
759
|
+
return false if File.basename(__FILE__) != File.basename($0)
|
760
|
+
|
761
|
+
binstub_spec = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'bin', File.basename(__FILE__)))
|
762
|
+
$0 == binstub_spec
|
763
|
+
end
|
764
|
+
|
765
|
+
|
766
|
+
# If this file is being called as a script, run it.
|
767
|
+
# Else, it may be loaded to use the model in a different way.
|
768
|
+
if running_as_script?
|
769
|
+
MacWifi::CommandLineInterface.new.call
|
770
|
+
end
|
771
|
+
|
data/mac-wifi.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
VERSION = '0.0.1'
|
3
|
+
|
4
|
+
Gem::Specification.new do |spec|
|
5
|
+
spec.name = "mac-wifi"
|
6
|
+
spec.version = VERSION
|
7
|
+
spec.authors = ["Keith Bennett"]
|
8
|
+
spec.email = ["keithrbennett@gmail.com"]
|
9
|
+
spec.description = %q{A command line interface for managing wifi on a Mac.}
|
10
|
+
spec.summary = %q{Mac wifi utility}
|
11
|
+
spec.homepage = "https://github.com/keithrbennett/mac-wifi"
|
12
|
+
spec.license = "MIT"
|
13
|
+
|
14
|
+
spec.files = `git ls-files`.split($/) - ['teaching_outline.md']
|
15
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
16
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
17
|
+
spec.require_paths = ['lib']
|
18
|
+
|
19
|
+
# spec.add_development_dependency "bundler", "~> 1.3"
|
20
|
+
# spec.add_development_dependency "rake", '~> 10.1'
|
21
|
+
# spec.add_development_dependency "rspec", '~> 3.0'
|
22
|
+
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# The functionality of this software is very difficult to test,
|
2
|
+
# sinece it relies on external conditions that cannot be faked.
|
3
|
+
# These tests merely run the commands and assert that no
|
4
|
+
# error has occurred; they don't make any attempt to verify the data.
|
5
|
+
# Many of them are run once with the wifi on, and once when it's off.
|
6
|
+
|
7
|
+
|
8
|
+
load File.join(File.dirname(__FILE__), '..', 'bin', 'mac-wifi')
|
9
|
+
|
10
|
+
module MacWifi
|
11
|
+
|
12
|
+
describe Model do
|
13
|
+
|
14
|
+
|
15
|
+
subject { Model.new }
|
16
|
+
|
17
|
+
context 'turning wifi on and off' do
|
18
|
+
it 'can turn wifi on' do
|
19
|
+
subject.wifi_off
|
20
|
+
expect(subject.wifi_on?).to eq(false)
|
21
|
+
subject.wifi_on
|
22
|
+
expect(subject.wifi_on?).to eq(true)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'can turn wifi off' do
|
26
|
+
subject.wifi_on
|
27
|
+
expect(subject.wifi_on?).to eq(true)
|
28
|
+
subject.wifi_off
|
29
|
+
expect(subject.wifi_on?).to eq(false)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'can cycle network' do
|
33
|
+
subject.wifi_on
|
34
|
+
subject.cycle_network
|
35
|
+
expect(subject.wifi_on?).to eq(true)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'can list available networks' do
|
39
|
+
subject.wifi_on
|
40
|
+
subject.available_network_info
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
shared_examples_for 'testing to see commands complete without error' do |wifi_starts_on|
|
46
|
+
|
47
|
+
it 'can determine if connected to Internet' do
|
48
|
+
wifi_starts_on ? subject.wifi_on : subject.wifi_off
|
49
|
+
# We cannot assert that we're connected to the Internet even
|
50
|
+
# if the wifi is on, because we're probably not connected to a network.
|
51
|
+
subject.connected_to_internet?
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'can get wifi port' do
|
55
|
+
wifi_starts_on ? subject.wifi_on : subject.wifi_off
|
56
|
+
subject.wifi_hardware_port
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'can list info' do
|
60
|
+
wifi_starts_on ? subject.wifi_on : subject.wifi_off
|
61
|
+
subject.wifi_info
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'can list preferred networks' do
|
65
|
+
wifi_starts_on ? subject.wifi_on : subject.wifi_off
|
66
|
+
subject.preferred_networks
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'can see if wifi is on' do
|
70
|
+
wifi_starts_on ? subject.wifi_on : subject.wifi_off
|
71
|
+
subject.wifi_on?
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'can query the connected network name' do
|
75
|
+
wifi_starts_on ? subject.wifi_on : subject.wifi_off
|
76
|
+
name = subject.connected_network_name
|
77
|
+
unless subject.wifi_on?
|
78
|
+
expect(name).to eq(nil)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# it 'can attempt to connect to a network' do
|
83
|
+
# pending 'cannot reliably expect any given network to be available'
|
84
|
+
# end
|
85
|
+
|
86
|
+
# it 'can determine the IP address on the network' do
|
87
|
+
# pending 'How to reliably reproduce connection to a network?'
|
88
|
+
# end
|
89
|
+
|
90
|
+
it 'can determine the current network' do
|
91
|
+
wifi_starts_on ? subject.wifi_on : subject.wifi_off
|
92
|
+
network = subject.current_network
|
93
|
+
unless subject.wifi_on?
|
94
|
+
expect(network).to eq(nil)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'can call disconnect twice consecutively' do
|
99
|
+
wifi_starts_on ? subject.wifi_on : subject.wifi_off
|
100
|
+
subject.disconnect
|
101
|
+
subject.disconnect
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'wifi starts on' do # without a context block the way that rspec expands the examples causes the parameters to overwrite each other
|
106
|
+
include_examples 'testing to see commands complete without error', true
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'wifi starts off' do # without a context block the way that rspec expands the examples causes the parameters to overwrite each other
|
110
|
+
include_examples 'testing to see commands complete without error', false
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
metadata
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mac-wifi
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Keith Bennett
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-09-17 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: A command line interface for managing wifi on a Mac.
|
14
|
+
email:
|
15
|
+
- keithrbennett@gmail.com
|
16
|
+
executables:
|
17
|
+
- mac-wifi
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- ".gitignore"
|
22
|
+
- Gemfile
|
23
|
+
- LICENSE.txt
|
24
|
+
- README.md
|
25
|
+
- bin/mac-wifi
|
26
|
+
- mac-wifi.gemspec
|
27
|
+
- spec/mac-wifi_spec.rb
|
28
|
+
homepage: https://github.com/keithrbennett/mac-wifi
|
29
|
+
licenses:
|
30
|
+
- MIT
|
31
|
+
metadata: {}
|
32
|
+
post_install_message:
|
33
|
+
rdoc_options: []
|
34
|
+
require_paths:
|
35
|
+
- lib
|
36
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
requirements: []
|
47
|
+
rubyforge_project:
|
48
|
+
rubygems_version: 2.6.13
|
49
|
+
signing_key:
|
50
|
+
specification_version: 4
|
51
|
+
summary: Mac wifi utility
|
52
|
+
test_files:
|
53
|
+
- spec/mac-wifi_spec.rb
|