mac-wifi 1.3.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/exe/mac-wifi CHANGED
@@ -19,13 +19,16 @@
19
19
  # License: MIT License
20
20
 
21
21
 
22
+ require 'json'
23
+ require 'yaml'
24
+
22
25
  require 'shellwords'
23
26
  require 'tempfile'
24
27
 
25
28
  module MacWifi
26
29
 
27
30
  # This version must be kept in sync with the version in the gemspec file.
28
- VERSION = '1.3.0'
31
+ VERSION = '2.0.0'
29
32
 
30
33
 
31
34
  class BaseModel
@@ -46,6 +49,19 @@ class BaseModel
46
49
  end
47
50
 
48
51
 
52
+ def run_os_command(command)
53
+ output = `#{command} 2>&1` # join stderr with stdout
54
+ if $?.exitstatus != 0
55
+ raise OsCommandError.new($?.exitstatus, command, output)
56
+ end
57
+ if @verbose_mode
58
+ puts "\n\n#{'-' * 79}\nCommand: #{command}\n\nOutput:\n#{output}#{'-' * 79}\n\n"
59
+ end
60
+ output
61
+ end
62
+ private :run_os_command
63
+
64
+
49
65
  # This method returns whether or not there is a working Internet connection.
50
66
  # Because of a Mac issue which causes a request to hang if the network is turned
51
67
  # off during its lifetime, we give it only 5 seconds per try,
@@ -135,7 +151,7 @@ class BaseModel
135
151
 
136
152
  # Connects to the passed network name, optionally with password.
137
153
  # Turns wifi on first, in case it was turned off.
138
- # Relies on subclass implementation of simple_connect().
154
+ # Relies on subclass implementation of os_level_connect().
139
155
  def connect(network_name, password = nil)
140
156
  # Allow symbols and anything responding to to_s for user convenience
141
157
  network_name = network_name.to_s if network_name
@@ -145,7 +161,7 @@ class BaseModel
145
161
  raise "A network name is required but was not provided."
146
162
  end
147
163
  wifi_on
148
- simple_connect(network_name, password)
164
+ os_level_connect(network_name, password)
149
165
 
150
166
  # Verify that the network is now connected:
151
167
  actual_network_name = connected_network_name
@@ -175,7 +191,7 @@ class BaseModel
175
191
  def preferred_network_password(preferred_network_name)
176
192
  preferred_network_name = preferred_network_name.to_s
177
193
  if preferred_networks.include?(preferred_network_name)
178
- simple_preferred_network_password(preferred_network_name)
194
+ os_level_preferred_network_password(preferred_network_name)
179
195
  else
180
196
  raise "Network #{preferred_network_name} not in preferred networks list."
181
197
  end
@@ -214,6 +230,21 @@ class BaseModel
214
230
  sleep(wait_interval_in_secs)
215
231
  end
216
232
  end
233
+
234
+
235
+ # Tries an OS command until the stop condition is true.
236
+ # @command the command to run in the OS
237
+ # @stop_condition a lambda taking the commands stdout as its sole parameter
238
+ # @return the stdout produced by the command
239
+ def try_os_command_until(command, stop_condition, max_tries = 100)
240
+ max_tries.times do
241
+ stdout = run_os_command(command)
242
+ if stop_condition.(stdout)
243
+ return stdout
244
+ end
245
+ end
246
+ nil
247
+ end
217
248
  end
218
249
 
219
250
 
@@ -266,37 +297,99 @@ class MacOsModel < BaseModel
266
297
  command = "#{AIRPORT_CMD} -s"
267
298
  max_attempts = 50
268
299
 
300
+
269
301
  reformat_line = ->(line) do
270
302
  ssid = line[0..31].strip
271
303
  "%-32.32s%s" % [ssid, line[32..-1]]
272
304
  end
273
305
 
274
- max_attempts.times do
275
- output = run_os_command(command)
276
- if output.size > 0
277
- lines = output.split("\n")
278
- header_line = lines[0]
279
- data_lines = lines[1..-1]
280
- data_lines.map! do |line|
281
- # Reformat the line so that the name is left instead of right justified
282
- reformat_line.(line)
283
- end
284
- data_lines.sort!
285
- return [reformat_line.(header_line)] + data_lines
306
+
307
+ process_tabular_data = ->(output) do
308
+ lines = output.split("\n")
309
+ header_line = lines[0]
310
+ data_lines = lines[1..-1]
311
+ data_lines.map! do |line|
312
+ # Reformat the line so that the name is left instead of right justified
313
+ reformat_line.(line)
286
314
  end
315
+ data_lines.sort!
316
+ [reformat_line.(header_line)] + data_lines
287
317
  end
288
318
 
289
319
 
290
- raise "Unable to get available network information after #{max_attempts} attempts."
320
+ output = try_os_command_until(command, ->(output) do
321
+ ! ([nil, ''].include?(output))
322
+ end)
323
+
324
+ if output
325
+ process_tabular_data.(output)
326
+ else
327
+ raise "Unable to get available network information after #{max_attempts} attempts."
328
+ end
291
329
  end
292
330
 
293
331
 
294
- # @return an array of unique available network names only, sorted alphabetically
295
- def available_network_names
296
- available_network_info[1..-1] \
332
+ def parse_network_names(info)
333
+ if info.nil?
334
+ nil
335
+ else
336
+ info[1..-1] \
297
337
  .map { |line| line[0..32].rstrip } \
298
338
  .uniq \
299
339
  .sort { |s1, s2| s1.casecmp(s2) }
340
+ end
341
+ end
342
+
343
+ # @return an array of unique available network names only, sorted alphabetically
344
+ # Kludge alert: the tabular data does not differentiate between strings with and without leading whitespace
345
+ # Therefore, we get the data once in tabular format, and another time in XML format.
346
+ # The XML element will include any leading whitespace. However, it includes all <string> elements,
347
+ # many of which are not network names.
348
+ # As an improved approximation of the correct result, for each network name found in tabular mode,
349
+ # we look to see if there is a corresponding string element with leading whitespace, and, if so,
350
+ # replace it.
351
+ #
352
+ # This will not behave correctly if a given name has occurrences with different amounts of whitespace,
353
+ # e.g. ' x' and ' x'.
354
+ #
355
+ # The reason we don't use an XML parser to get the exactly correct result is that we don't want
356
+ # users to need to install any external dependencies in order to run this script.
357
+ def available_network_names
358
+
359
+ # Parses the XML text (using grep, not XML parsing) to find
360
+ # <string> elements, and extracts the network name candidates
361
+ # containing leading spaces from it.
362
+ get_leading_space_names = ->(text) do
363
+ text.split("\n") \
364
+ .grep(%r{<string>}) \
365
+ .sort \
366
+ .uniq \
367
+ .map { |line| line.gsub("<string>", '').gsub('</string>', '').gsub("\t", '') } \
368
+ .select { |s| s[0] == ' ' }
369
+ end
370
+
371
+
372
+ output_is_valid = ->(output) { ! ([nil, ''].include?(output)) }
373
+ tabular_data = try_os_command_until("#{AIRPORT_CMD} -s", output_is_valid)
374
+ xml_data = try_os_command_until("#{AIRPORT_CMD} -s -x", output_is_valid)
375
+
376
+ if tabular_data.nil? || xml_data.nil?
377
+ raise "Unable to get available network information; please try again."
378
+ end
379
+
380
+ tabular_data_lines = tabular_data[1..-1] # omit header line
381
+ names_no_spaces = parse_network_names(tabular_data_lines.split("\n")).map(&:strip)
382
+ names_maybe_spaces = get_leading_space_names.(xml_data)
383
+
384
+ names = names_no_spaces.map do |name_no_spaces|
385
+ match = names_maybe_spaces.detect do |name_maybe_spaces|
386
+ %r{[ \t]?#{name_no_spaces}$}.match(name_maybe_spaces)
387
+ end
388
+
389
+ match ? match : name_no_spaces
390
+ end
391
+
392
+ names.sort { |s1, s2| s1.casecmp(s2) } # sort alphabetically, case insensitively
300
393
  end
301
394
 
302
395
 
@@ -344,7 +437,7 @@ class MacOsModel < BaseModel
344
437
 
345
438
 
346
439
  # This method is called by BaseModel#connect to do the OS-specific connection logic.
347
- def simple_connect(network_name, password = nil)
440
+ def os_level_connect(network_name, password = nil)
348
441
  command = "networksetup -setairportnetwork #{wifi_hardware_port} " + "#{Shellwords.shellescape(network_name)}"
349
442
  if password
350
443
  command << ' ' << Shellwords.shellescape(password)
@@ -359,7 +452,7 @@ class MacOsModel < BaseModel
359
452
  # If not, return nil
360
453
  # else
361
454
  # raise an error
362
- def simple_preferred_network_password(preferred_network_name)
455
+ def os_level_preferred_network_password(preferred_network_name)
363
456
  command = %Q{security find-generic-password -D "AirPort network password" -a "#{preferred_network_name}" -w 2>&1}
364
457
  begin
365
458
  return run_os_command(command).chomp
@@ -429,19 +522,6 @@ class MacOsModel < BaseModel
429
522
  end
430
523
 
431
524
 
432
- def run_os_command(command)
433
- output = `#{command} 2>&1` # join stderr with stdout
434
- if $?.exitstatus != 0
435
- raise OsCommandError.new($?.exitstatus, command, output)
436
- end
437
- if @verbose_mode
438
- puts "\n\n#{'-' * 79}\nCommand: #{command}\n\nOutput:\n#{output}#{'-' * 79}\n\n"
439
- end
440
- output
441
- end
442
- private :run_os_command
443
-
444
-
445
525
  # Parses output like the text below into a hash:
446
526
  # SSID: Pattara211
447
527
  # MCS: 5
@@ -461,7 +541,7 @@ end
461
541
 
462
542
  class CommandLineInterface
463
543
 
464
- attr_reader :model, :interactive_mode
544
+ attr_reader :model, :interactive_mode, :options
465
545
 
466
546
  class Command < Struct.new(:min_string, :max_string, :action); end
467
547
 
@@ -475,23 +555,29 @@ class CommandLineInterface
475
555
 
476
556
  # Help text to be used when requested by 'h' command, in case of unrecognized or nonexistent command, etc.
477
557
  HELP_TEXT = "
478
- mac-wifi version #{VERSION} -- Available commands are:
558
+ Command Line Switches: [mac-wifi version #{VERSION}]
559
+
560
+ -o[i,j,p,y] - outputs data in inspect, JSON, puts, or YAML format when not in shell mode
561
+ -s - run in shell mode
562
+ -v - verbose mode (prints OS commands and their outputs)
563
+
564
+ Commands:
479
565
 
480
- a[vailnets] - array of names of the available networks
566
+ a[vail_nets] - array of names of the available networks
481
567
  ci - connected to Internet (not just wifi on)?
482
568
  co[nnect] network-name - turns wifi on, connects to network-name
483
569
  cy[cle] - turns wifi off, then on, preserving network selection
484
570
  d[isconnect] - disconnects from current network, does not turn off wifi
485
571
  h[elp] - prints this help
486
572
  i[nfo] - a hash of wifi-related information
487
- l[savailnets] - details about available networks
573
+ l[s_avail_nets] - details about available networks
488
574
  n[etwork_name] - name (SSID) of currently connected network
489
575
  on - turns wifi on
490
576
  of[f] - turns wifi off
491
577
  pa[ssword] network-name - password for preferred network-name
492
- pr[efnets] - preferred (not necessarily available) networks
578
+ pr[ef_nets] - preferred (not necessarily available) networks
493
579
  q[uit] - exits this program (interactive shell mode only) (see also 'x')
494
- r[mprefnets] network-name - removes network-name from the preferred networks list
580
+ r[m_pref_nets] network-name - removes network-name from the preferred networks list
495
581
  (can provide multiple names separated by spaces)
496
582
  s[hell] - opens an interactive pry shell (command line only)
497
583
  t[ill] - returns when the desired Internet connection state is true. Options:
@@ -507,16 +593,18 @@ When in interactive shell mode:
507
593
  "
508
594
 
509
595
 
510
- def initialize
596
+ def initialize(options)
597
+ @options = options
511
598
  @model = MacOsModel.new(verbose_mode)
512
- @interactive_mode = false # will be changed to true if/when user selects shell option on command line
599
+ @interactive_mode = !!(options.interactive_mode)
600
+ run_shell if @interactive_mode
513
601
  end
514
602
 
515
603
 
516
604
  # Until command line option parsing is added, the only way to specify
517
605
  # verbose mode is in the environment variable MAC_WIFI_OPTS.
518
606
  def verbose_mode
519
- /-v/.match(ENV['MAC_WIFI_OPTS'])
607
+ options.verbose
520
608
  end
521
609
 
522
610
 
@@ -563,7 +651,7 @@ When in interactive shell mode:
563
651
  # Asserts that a command has been passed on the command line.
564
652
  def validate_command_line
565
653
  if ARGV.empty?
566
- puts "Syntax is: #{__FILE__} command [options]"
654
+ puts "Syntax is: #{__FILE__} [options] command [command_options]"
567
655
  print_help
568
656
  exit(-1)
569
657
  end
@@ -575,23 +663,16 @@ When in interactive shell mode:
575
663
  # that is not needed here.
576
664
  def run_pry
577
665
  binding.pry
666
+
667
+ # the seemingly useless line below is needed to avoid pry's exiting
668
+ # (see https://github.com/deivid-rodriguez/pry-byebug/issues/45)
669
+ _a = nil
578
670
  end
579
671
 
580
672
 
581
673
  # Runs a pry session in the context of this object.
582
674
  # Commands and options specified on the command line can also be specified in the shell.
583
675
  def run_shell
584
- if interactive_mode
585
- puts "Already in shell."
586
- return
587
- end
588
-
589
- @interactive_mode = true
590
-
591
- # For conveniently calling to_json or to_yaml on an object:
592
- require 'json'
593
- require 'yaml'
594
-
595
676
  begin
596
677
  require 'pry'
597
678
  rescue LoadError
@@ -656,15 +737,30 @@ When in interactive shell mode:
656
737
 
657
738
  def cmd_a
658
739
  info = model.available_network_names
659
- puts_unless_interactive(fancy_string(info))
660
- info
740
+ if interactive_mode
741
+ info
742
+ else
743
+ if post_processor
744
+ puts post_processor.(info)
745
+ else
746
+ message = if model.wifi_on?
747
+ "Available networks are:\n\n#{fancy_string(info)}"
748
+ else
749
+ "Wifi is off, cannot see available networks."
750
+ end
751
+ puts(message)
752
+ end
753
+ end
661
754
  end
662
755
 
663
756
 
664
757
  def cmd_ci
665
758
  connected = model.connected_to_internet?
666
- puts_unless_interactive("Connected to Internet: #{connected}")
667
- connected
759
+ if interactive_mode
760
+ connected
761
+ else
762
+ puts (post_processor ? post_processor.(connected) : "Connected to Internet: #{connected}")
763
+ end
668
764
  end
669
765
 
670
766
 
@@ -690,22 +786,41 @@ When in interactive shell mode:
690
786
 
691
787
  def cmd_i
692
788
  info = model.wifi_info
693
- puts_unless_interactive(fancy_string(info))
694
- info
789
+ if interactive_mode
790
+ info
791
+ else
792
+ if post_processor
793
+ puts post_processor.(info)
794
+ else
795
+ puts fancy_string(info)
796
+ end
797
+ end
695
798
  end
696
799
 
697
800
 
698
801
  def cmd_lsa
699
802
  info = model.available_network_info
700
- puts_unless_interactive(fancy_string(info))
701
- info
803
+ if interactive_mode
804
+ info
805
+ else
806
+ if post_processor
807
+ puts post_processor.(info)
808
+ else
809
+ message = model.wifi_on? ? fancy_string(info) : "Wifi is off, cannot see available networks."
810
+ puts(message)
811
+ end
812
+ end
702
813
  end
703
814
 
704
815
 
705
816
  def cmd_n
706
817
  name = model.connected_network_name
707
- puts_unless_interactive("Network (SSID) name: #{name ? name : '[none]'}")
708
- name
818
+ if interactive_mode
819
+ name
820
+ else
821
+ display_name = name ? name : '[none]'
822
+ puts (post_processor ? post_processor.(name) : %Q{Network (SSID) name: "#{display_name}"})
823
+ end
709
824
  end
710
825
 
711
826
 
@@ -722,20 +837,27 @@ When in interactive shell mode:
722
837
  def cmd_pa(network)
723
838
  password = model.preferred_network_password(network)
724
839
 
725
- unless interactive_mode
726
- output = %Q{Preferred network "#{network_name}" }
727
- output << (password ? %Q{stored password is: "#{password}".} : "has no stored password.")
728
- puts output
840
+ if interactive_mode
841
+ password
842
+ else
843
+ if post_processor
844
+ puts post_processor.(password)
845
+ else
846
+ output = %Q{Preferred network "#{model.connected_network_name}" }
847
+ output << (password ? %Q{stored password is "#{password}".} : "has no stored password.")
848
+ puts output
849
+ end
729
850
  end
730
-
731
- password
732
851
  end
733
852
 
734
853
 
735
854
  def cmd_pr
736
855
  networks = model.preferred_networks
737
- puts_unless_interactive(fancy_string(networks))
738
- networks
856
+ if interactive_mode
857
+ networks
858
+ else
859
+ puts (post_processor ? post_processor.(networks) : fancy_string(networks))
860
+ end
739
861
  end
740
862
 
741
863
 
@@ -745,12 +867,12 @@ When in interactive shell mode:
745
867
 
746
868
 
747
869
  def cmd_r(*options)
748
- model.remove_preferred_networks(*options)
749
- end
750
-
751
-
752
- def cmd_s
753
- run_shell
870
+ removed_networks = model.remove_preferred_networks(*options)
871
+ if interactive_mode
872
+ removed_networks
873
+ else
874
+ puts (post_processor ? post_processor.(removed_networks) : "Removed networks: #{removed_networks.inspect}")
875
+ end
754
876
  end
755
877
 
756
878
 
@@ -763,8 +885,11 @@ When in interactive shell mode:
763
885
 
764
886
  def cmd_w
765
887
  on = model.wifi_on?
766
- puts_unless_interactive("Wifi on: #{on}")
767
- on
888
+ if interactive_mode
889
+ on
890
+ else
891
+ puts (post_processor ? post_processor.(on) : "Wifi on: #{on}")
892
+ end
768
893
  end
769
894
 
770
895
 
@@ -775,25 +900,24 @@ When in interactive shell mode:
775
900
 
776
901
  def commands
777
902
  @commands_ ||= [
778
- Command.new('a', 'availnets', -> (*_options) { cmd_a }),
779
- Command.new('ci', 'ci', -> (*_options) { cmd_ci }),
780
- Command.new('co', 'connect', -> (*options) { cmd_co(*options) }),
781
- Command.new('cy', 'cycle', -> (*_options) { cmd_cy }),
782
- Command.new('d', 'disconnect', -> (*_options) { cmd_d }),
783
- Command.new('h', 'help', -> (*_options) { cmd_h }),
784
- Command.new('i', 'info', -> (*_options) { cmd_i }),
785
- Command.new('l', 'lsavailnets', -> (*_options) { cmd_lsa }),
786
- Command.new('n', 'network_name', -> (*_options) { cmd_n }),
787
- Command.new('of', 'off', -> (*_options) { cmd_of }),
788
- Command.new('on', 'on', -> (*_options) { cmd_on }),
789
- Command.new('pa', 'password', -> (*options) { cmd_pa(*options) }),
790
- Command.new('pr', 'prefnets', -> (*_options) { cmd_pr }),
791
- Command.new('q', 'quit', -> (*_options) { cmd_q }),
792
- Command.new('r', 'rmprefnets', -> (*options) { cmd_r(*options) }),
793
- Command.new('s', 'shell', -> (*_options) { cmd_s }),
794
- Command.new('t', 'till', -> (*options) { cmd_t(*options) }),
795
- Command.new('w', 'wifion', -> (*_options) { cmd_w }),
796
- Command.new('x', 'xit', -> (*_options) { cmd_x })
903
+ Command.new('a', 'avail_nets', -> (*_options) { cmd_a }),
904
+ Command.new('ci', 'ci', -> (*_options) { cmd_ci }),
905
+ Command.new('co', 'connect', -> (*options) { cmd_co(*options) }),
906
+ Command.new('cy', 'cycle', -> (*_options) { cmd_cy }),
907
+ Command.new('d', 'disconnect', -> (*_options) { cmd_d }),
908
+ Command.new('h', 'help', -> (*_options) { cmd_h }),
909
+ Command.new('i', 'info', -> (*_options) { cmd_i }),
910
+ Command.new('l', 'ls_avail_nets', -> (*_options) { cmd_lsa }),
911
+ Command.new('n', 'network_name', -> (*_options) { cmd_n }),
912
+ Command.new('of', 'off', -> (*_options) { cmd_of }),
913
+ Command.new('on', 'on', -> (*_options) { cmd_on }),
914
+ Command.new('pa', 'password', -> (*options) { cmd_pa(*options) }),
915
+ Command.new('pr', 'pref_nets', -> (*_options) { cmd_pr }),
916
+ Command.new('q', 'quit', -> (*_options) { cmd_q }),
917
+ Command.new('r', 'rm_pref_nets', -> (*options) { cmd_r(*options) }),
918
+ Command.new('t', 'till', -> (*options) { cmd_t(*options) }),
919
+ Command.new('w', 'wifion', -> (*_options) { cmd_w }),
920
+ Command.new('x', 'xit', -> (*_options) { cmd_x })
797
921
  ]
798
922
  end
799
923
 
@@ -809,6 +933,18 @@ When in interactive shell mode:
809
933
  end
810
934
 
811
935
 
936
+ # If a post-processor has been configured (e.g. YAML or JSON), use it.
937
+ def post_process(object)
938
+ post_processor ? post_processor.(object) : object
939
+ end
940
+
941
+
942
+
943
+ def post_processor
944
+ options.post_processor
945
+ end
946
+
947
+
812
948
  def call
813
949
  validate_command_line
814
950
  begin
@@ -822,6 +958,9 @@ When in interactive shell mode:
822
958
  end
823
959
 
824
960
 
961
+ require 'optparse'
962
+ require 'ostruct'
963
+
825
964
  class Main
826
965
 
827
966
  # @return true if this file is being run as a script, else false
@@ -873,20 +1012,57 @@ class Main
873
1012
  end
874
1013
 
875
1014
 
1015
+ # Parses the command line with Ruby's internal 'optparse'.
1016
+ # Looks for "-v" flag to set verbosity to true.
1017
+ # optparse removes what it processes from ARGV, which simplifies our command parsing.
1018
+ def parse_command_line
1019
+ options = OpenStruct.new
1020
+ OptionParser.new do |parser|
1021
+ parser.on("-v", "--[no-]verbose", "Run verbosely") do |v|
1022
+ options.verbose = v
1023
+ end
1024
+
1025
+ parser.on("-s", "--shell", "Start interactive shell") do |v|
1026
+ options.interactive_mode = true
1027
+ end
1028
+
1029
+ parser.on("-o", "--output_format FORMAT", "Format output data") do |v|
1030
+
1031
+ transformers = {
1032
+ 'i' => ->(object) { object.inspect },
1033
+ 'j' => ->(object) { JSON.pretty_generate(object) },
1034
+ 'p' => ->(object) { sio = StringIO.new; sio.puts(object); sio.string },
1035
+ 'y' => ->(object) { object.to_yaml }
1036
+ }
1037
+
1038
+ choice = v[0].downcase
1039
+
1040
+ unless transformers.keys.include?(choice)
1041
+ raise %Q{Output format "#{choice}" not in list of available formats} +
1042
+ " (#{transformers.keys.inspect})."
1043
+ end
1044
+
1045
+ options.post_processor = transformers[choice]
1046
+ end
1047
+
1048
+ parser.on("-h", "--help", "Show help") do |_help_requested|
1049
+ ARGV << 'h' # pass on the request to the command processor
1050
+ end
1051
+ end.parse!
1052
+ options
1053
+ end
1054
+
1055
+
876
1056
  def call
877
1057
  assert_os_is_mac_os
878
1058
 
1059
+ options = parse_command_line
1060
+
879
1061
  # If this file is being called as a script, run it.
880
1062
  # Else, it may be loaded to use the model in a different way.
881
1063
  if running_as_script?
882
1064
  begin
883
- MacWifi::CommandLineInterface.new.call
884
- rescue => error
885
- puts error
886
- stack_trace_filename = './mac-wifi-error-stack-trace.txt'
887
- File.write(stack_trace_filename, caller.join("\n"))
888
- puts "Exiting. Stack trace written to '#{File.expand_path(stack_trace_filename)}'."
889
- exit(-1)
1065
+ MacWifi::CommandLineInterface.new(options).call
890
1066
  end
891
1067
  end
892
1068
  end
data/mac-wifi.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  # coding: utf-8
2
2
  # When changing the version, also change the version and the help text in the README.
3
- VERSION = '1.3.0'
3
+ VERSION = '2.0.0'
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "mac-wifi"